New Upstream Release - dropwizard-metrics
Ready changes
Summary
Merged new upstream version: 4.2.25 (was: 3.2.6).
Diff
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 0000000..1230149
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/.github/workflows/close_stale.yml b/.github/workflows/close_stale.yml
new file mode 100644
index 0000000..38cf96d
--- /dev/null
+++ b/.github/workflows/close_stale.yml
@@ -0,0 +1,21 @@
+name: "Close stale issues"
+on:
+ schedule:
+ - cron: "0 0 * * *"
+permissions:
+ contents: read
+
+jobs:
+ stale:
+ permissions:
+ issues: write # for actions/stale to close stale issues
+ pull-requests: write # for actions/stale to close stale PRs
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ stale-issue-message: 'This issue is stale because it has been open 180 days with no activity. Remove the "stale" label or comment or this will be closed in 14 days.'
+ stale-pr-message: 'This pull request is stale because it has been open 180 days with no activity. Remove the "stale" label or comment or this will be closed in 14 days.'
+ days-before-stale: 180
+ days-before-close: 14
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 0000000..a57819d
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,52 @@
+name: Java CI
+on:
+ pull_request:
+ branches:
+ - release/*
+ push:
+ branches:
+ - release/*
+permissions:
+ contents: read
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ java_version: [11, 17, 21]
+ os:
+ - ubuntu-latest
+ env:
+ JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
+ with:
+ fetch-depth: 0
+ - name: Set up JDK
+ uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4
+ with:
+ distribution: 'zulu'
+ java-version: ${{ matrix.java_version }}
+ - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Cache SonarCloud packages
+ uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
+ if: ${{ env.SONAR_TOKEN != null && env.SONAR_TOKEN != '' && matrix.java_version == '17' }}
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ with:
+ path: ~/.sonar/cache
+ key: ${{ runner.os }}-sonar
+ restore-keys: ${{ runner.os }}-sonar
+ - name: Build
+ run: ./mvnw -B -V -ff -ntp install javadoc:javadoc
+ - name: Analyze with SonarCloud
+ if: ${{ env.SONAR_TOKEN != null && env.SONAR_TOKEN != '' && matrix.java_version == '17' }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ run: ./mvnw -B -ff -ntp org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml
new file mode 100644
index 0000000..5591fee
--- /dev/null
+++ b/.github/workflows/qodana.yml
@@ -0,0 +1,29 @@
+---
+name: "Qodana"
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - "main"
+ - 'release/*'
+
+jobs:
+ qodana:
+ runs-on: "ubuntu-latest"
+ permissions:
+ contents: "write"
+ pull-requests: "write"
+ checks: "write"
+ security-events: "write"
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
+ with:
+ fetch-depth: 0
+ - name: 'Qodana Scan'
+ uses: JetBrains/qodana-action@v2023.2
+ env:
+ QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
+ - uses: github/codeql-action/upload-sarif@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3
+ with:
+ sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..7ea360a
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,39 @@
+name: Release
+on:
+ push:
+ branches:
+ - release/4.1.x
+ - release/4.2.x
+ - release/5.0.x
+permissions:
+ contents: read
+
+jobs:
+ release:
+ runs-on: 'ubuntu-latest'
+ env:
+ JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
+ - name: Set up JDK
+ uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4
+ with:
+ java-version: 17
+ distribution: 'zulu'
+ server-id: ossrh
+ server-username: CI_DEPLOY_USERNAME
+ server-password: CI_DEPLOY_PASSWORD
+ gpg-passphrase: GPG_PASSPHRASE
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+ - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Build and Deploy
+ run: ./mvnw -B -V -ntp -DperformRelease=true deploy
+ env:
+ CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }}
+ CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }}
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
diff --git a/.github/workflows/trigger-release.yml b/.github/workflows/trigger-release.yml
new file mode 100644
index 0000000..2de94f7
--- /dev/null
+++ b/.github/workflows/trigger-release.yml
@@ -0,0 +1,48 @@
+name: Trigger Release
+on:
+ workflow_dispatch:
+ inputs:
+ releaseVersion:
+ description: 'Version of the next release'
+ required: true
+ developmentVersion:
+ description: 'Version of the next development cycle (must end in "-SNAPSHOT")'
+ required: true
+jobs:
+ trigger-release:
+ runs-on: 'ubuntu-latest'
+ permissions:
+ contents: write
+ env:
+ JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
+ steps:
+ - uses: webfactory/ssh-agent@d4b9b8ff72958532804b70bbe600ad43b36d5f2e # v0.8.0
+ with:
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
+ - name: Set up JDK
+ uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ cache: 'maven'
+ server-id: ossrh
+ server-username: ${{ secrets.CI_DEPLOY_USERNAME }}
+ server-password: ${{ secrets.CI_DEPLOY_PASSWORD }}
+ gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+ - name: Set up Git
+ run: |
+ git config --global committer.email "48418865+dropwizard-committers@users.noreply.github.com"
+ git config --global committer.name "Dropwizard Release Action"
+ git config --global author.email "${GITHUB_ACTOR}@users.noreply.github.com"
+ git config --global author.name "${GITHUB_ACTOR}"
+ - name: Prepare release
+ run: ./mvnw -V -B -ntp -Prelease -DreleaseVersion=${{ inputs.releaseVersion }} -DdevelopmentVersion=${{ inputs.developmentVersion }} release:prepare
+ - name: Rollback on failure
+ if: failure()
+ run: |
+ ./mvnw -B release:rollback -Prelease
+ echo "You may need to manually delete the GitHub tag, if it was created."
diff --git a/.gitignore b/.gitignore
index 6b9fbe5..fedb7d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,5 @@ bin
.metadata
jcstress.*
metrics-jcstress/results/
-TODO.md
\ No newline at end of file
+TODO.md
+.mvn/wrapper/maven-wrapper.jar
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 0000000..6db6e66
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,138 @@
+## Learn more about this file at 'https://www.gitpod.io/docs/references/gitpod-yml'
+##
+## This '.gitpod.yml' file when placed at the root of a project instructs
+## Gitpod how to prepare & build the project, start development environments
+## and configure continuous prebuilds. Prebuilds when enabled builds a project
+## like a CI server so you can start coding right away - no more waiting for
+## dependencies to download and builds to finish when reviewing pull-requests
+## or hacking on something new.
+##
+## With Gitpod you can develop software from any device (even iPads) via
+## desktop or browser based versions of VS Code or any JetBrains IDE and
+## customise it to your individual needs - from themes to extensions, you
+## have full control.
+##
+## The easiest way to try out Gitpod is install the browser extenion:
+## 'https://www.gitpod.io/docs/browser-extension' or by prefixing
+## 'https://gitpod.io#' to the source control URL of any project.
+##
+## For example: 'https://gitpod.io#https://github.com/gitpod-io/gitpod'
+
+
+## The 'image' section defines which Docker image Gitpod should use.
+## By default, Gitpod uses a standard Docker Image called 'workspace-full'
+## which can be found at 'https://github.com/gitpod-io/workspace-images'
+##
+## Workspaces started based on this default image come pre-installed with
+## Docker, Go, Java, Node.js, C/C++, Python, Ruby, Rust, PHP as well as
+## tools such as Homebrew, Tailscale, Nginx and several more.
+##
+## If this image does not include the tools needed for your project then
+## a public Docker image or your own Docker file can be configured.
+##
+## Learn more about images at 'https://www.gitpod.io/docs/config-docker'
+
+#image: node:buster # use 'https://hub.docker.com/_/node'
+#
+#image: # leave image undefined if using a Dockerfile
+# file: .gitpod.Dockerfile # relative path to the Dockerfile from the
+# # root of the project
+
+## The 'tasks' section defines how Gitpod prepares and builds this project
+## or how Gitpod can start development servers. With Gitpod, there are three
+## types of tasks:
+##
+## - before: Use this for tasks that need to run before init and before command.
+## - init: Use this to configure prebuilds of heavy-lifting tasks such as
+## downloading dependencies or compiling source code.
+## - command: Use this to start your database or application when the workspace starts.
+##
+## Learn more about these tasks at 'https://www.gitpod.io/docs/config-start-tasks'
+
+#tasks:
+# - before: |
+# # commands to execute...
+#
+# - init: |
+# # sudo apt-get install python3 # can be used to install operating system
+# # dependencies but these are not kept after the
+# # prebuild completes thus Gitpod recommends moving
+# # operating system dependency installation steps
+# # to a custom Dockerfile to make prebuilds faster
+# # and to keep your codebase DRY.
+# # 'https://www.gitpod.io/docs/config-docker'
+#
+# # pip install -r requirements.txt # install codebase dependencies
+# # cmake # precompile codebase
+#
+# - name: Web Server
+# openMode: split-left
+# env:
+# WEBSERVER_PORT: 8080
+# command: |
+# python3 -m http.server $WEBSERVER_PORT
+#
+# - name: Web Browser
+# openMode: split-right
+# env:
+# WEBSERVER_PORT: 8080
+# command: |
+# gp await-port $WEBSERVER_PORT
+# lynx `gp url`
+
+tasks:
+ - init: ./mvnw package -DskipTests
+
+## The 'ports' section defines various ports your may listen on are
+## configured in Gitpod on an authenticated URL. By default, all ports
+## are in private visibility state.
+##
+## Learn more about ports at 'https://www.gitpod.io/docs/config-ports'
+
+#ports:
+# - port: 8080 # alternatively configure entire ranges via '8080-8090'
+# visibility: private # either 'public' or 'private' (default)
+# onOpen: open-browser # either 'open-browser', 'open-preview' or 'ignore'
+
+
+## The 'vscode' section defines a list of Visual Studio Code extensions from
+## the OpenVSX.org registry to be installed upon workspace startup. OpenVSX
+## is an open alternative to the proprietary Visual Studio Code Marketplace
+## and extensions can be added by sending a pull-request with the extension
+## identifier to https://github.com/open-vsx/publish-extensions
+##
+## The identifier of an extension is always ${publisher}.${name}.
+##
+## For example: 'vscodevim.vim'
+##
+## Learn more at 'https://www.gitpod.io/docs/ides-and-editors/vscode'
+
+vscode:
+ extensions:
+ - redhat.java
+ - vscjava.vscode-java-pack
+ - lextudio.restructuredtext
+
+## The 'github' section defines configuration of continuous prebuilds
+## for GitHub repositories when the GitHub application
+## 'https://github.com/apps/gitpod-io' is installed in GitHub and granted
+## permissions to access the repository.
+##
+## Learn more at 'https://www.gitpod.io/docs/prebuilds'
+
+github:
+ prebuilds:
+ # enable for the default branch
+ master: true
+ # enable for all branches in this repo
+ branches: true
+ # enable for pull requests coming from this repo
+ pullRequests: true
+ # enable for pull requests coming from forks
+ pullRequestsFromForks: true
+ # add a check to pull requests
+ addCheck: true
+ # add a "Review in Gitpod" button as a comment to pull requests
+ addComment: true
+ # add a "Review in Gitpod" button to the pull request's description
+ addBadge: false
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..b901097
--- /dev/null
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..346d645
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..2cb337a
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,26 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the version of Python and other tools you might need
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+
+# Build documentation in the docs/source/ directory with Sphinx
+sphinx:
+ configuration: docs/source/conf.py
+ builder: dirhtml
+
+# If using Sphinx, optionally build your docs in additional formats such as PDF
+# formats:
+# - pdf
+
+# Optionally declare the Python requirements required to build your docs
+python:
+ install:
+ - requirements: docs/requirements.txt
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 16c7e43..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-language: java
-
-install: echo "I trust Maven."
-
-# don't just run the tests, also run Findbugs and friends
-script: mvn verify
-
-jdk:
- - oraclejdk8
-
-notifications:
- email:
- recipients:
- - ryan@10e.us
-
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..2dca904
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+* @dropwizard/metrics @dropwizard/committers
diff --git a/LICENSE b/LICENSE
index e4ba404..7cf513b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2010-2012 Coda Hale and Yammer, Inc.
+ Copyright 2010-2013 Coda Hale and Yammer, Inc., 2014-2020 Dropwizard Team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 4fe83de..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,11 +0,0 @@
-Metrics
-Copyright 2010-2013 Coda Hale and Yammer, Inc.
-
-This product includes software developed by Coda Hale and Yammer, Inc.
-
-This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
-LongAdder), which was released with the following comments:
-
- Written by Doug Lea with assistance from members of JCP JSR-166
- Expert Group and released to the public domain, as explained at
- http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/README.md b/README.md
index 7378e9b..97e09bd 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,37 @@
-Metrics [![Build Status](https://secure.travis-ci.org/dropwizard/metrics.png)](http://travis-ci.org/dropwizard/metrics)
-[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.dropwizard.metrics/metrics-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.dropwizard.metrics/metrics-core/)
-=======
+Metrics
+=======
+[![Java CI](https://github.com/dropwizard/metrics/workflows/Java%20CI/badge.svg)](https://github.com/dropwizard/metrics/actions?query=workflow%3A%22Java+CI%22+branch%3Arelease%2F4.2.x)
+[![Maven Central](https://img.shields.io/maven-central/v/io.dropwizard.metrics/metrics-core/4.2)](https://maven-badges.herokuapp.com/maven-central/io.dropwizard.metrics/metrics-core/)
+[![Javadoc](http://javadoc-badge.appspot.com/io.dropwizard.metrics/metrics-core.svg)](http://www.javadoc.io/doc/io.dropwizard.metrics/metrics-core)
+[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/dropwizard/metrics/tree/release/4.2.x)
-*Capturing JVM- and application-level metrics. So you know what's going on.*
+*π Capturing JVM- and application-level metrics. So you know what's going on.*
-For more information, please see [the documentation](http://dropwizard.github.io/metrics/).
+For more information, please see [the documentation](https://metrics.dropwizard.io/)
+### Versions
+
+| Version | Source Branch | Documentation | Status |
+| ------- | -------------------------------------------------------------------------------- | --------------------------------------------- | ----------------- |
+| <2.2.x | - | - | π΄ unmaintained |
+| 2.2.x | - | [Docs](https://metrics.dropwizard.io/2.2.0/) | π΄ unmaintained |
+| 3.0.x | [release/3.0.x branch](https://github.com/dropwizard/metrics/tree/release/3.0.x) | [Docs](https://metrics.dropwizard.io/3.0.2/) | π΄ unmaintained |
+| 3.1.x | [release/3.1.x branch](https://github.com/dropwizard/metrics/tree/release/3.1.x) | [Docs](https://metrics.dropwizard.io/3.1.0/) | π΄ unmaintained |
+| 3.2.x | [release/3.2.x branch](https://github.com/dropwizard/metrics/tree/release/3.2.x) | [Docs](https://metrics.dropwizard.io/3.2.3/) | π΄ unmaintained |
+| 4.0.x | [release/4.0.x branch](https://github.com/dropwizard/metrics/tree/release/4.0.x) | [Docs](https://metrics.dropwizard.io/4.0.6/) | π΄ unmaintained |
+| 4.1.x | [release/4.1.x branch](https://github.com/dropwizard/metrics/tree/release/4.1.x) | [Docs](https://metrics.dropwizard.io/4.1.22/) | π΄ unmaintained |
+| 4.2.x | [release/4.2.x branch](https://github.com/dropwizard/metrics/tree/release/4.2.x) | [Docs](https://metrics.dropwizard.io/4.2.0/) | π’ maintained |
+| 5.0.x | [release/5.0.x branch](https://github.com/dropwizard/metrics/tree/release/5.0.x) | - | π‘ on pause |
+
+### Future development
+
+New not-backward compatible features (for example, support for tags) will be implemented in a 5.x.x release. The release will have new Maven coordinates, a new package name and a backwards-incompatible API.
+
+Source code for 5.x.x resides in the [release/5.0.x branch](https://github.com/dropwizard/metrics/tree/release/5.0.x).
License
-------
-Copyright (c) 2010-2013 Coda Hale, Yammer.com
+Copyright (c) 2010-2013 Coda Hale, Yammer.com, 2014-2021 Dropwizard Team
Published under Apache Software License 2.0, see LICENSE
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..15f899e
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,20 @@
+# Security Policy
+
+## Supported Versions
+
+In general, only the currently stable version is supported.
+
+| Version | Supported |
+| ------- | ------------------ |
+| 5.0.x | :x: (in development) |
+| 4.2.x | :white_check_mark: |
+| < 4.2 | :x: |
+
+## Reporting a Vulnerability
+
+To responsibly disclose security issues in Dropwizard Metrics, you can use the following contacts:
+
+* Send an email to dropwizard.committers+security@gmail.com
+* Send a direct message on Twitter: [@dropwizardio](https://twitter.com/dropwizardio)
+
+We'll be contacting you as fast as possible after receiving your message.
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 8f23dd2..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-test_script:
- - mvn package
-build: off
\ No newline at end of file
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..2e6b434
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC
+ "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+ "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+<module name="Checker">
+ <property name="severity" value="warning"/>
+ <property name="localeLanguage" value="en"/>
+
+ <module name="FileTabCharacter">
+ <property name="fileExtensions" value="java"/>
+ </module>
+
+ <module name="TreeWalker">
+
+ <!-- code cleanup -->
+ <module name="UnusedImports">
+ <property name="processJavadoc" value="true"/>
+ </module>
+ <module name="RedundantImport"/>
+ <module name="IllegalImport"/>
+ <module name="EqualsHashCode"/>
+ <module name="SimplifyBooleanExpression"/>
+ <module name="OneStatementPerLine"/>
+ <module name="UnnecessaryParentheses"/>
+ <module name="SimplifyBooleanReturn"/>
+
+ <!-- style -->
+ <module name="DefaultComesLast"/>
+ <module name="EmptyStatement"/>
+ <module name="ArrayTypeStyle"/>
+ <module name="UpperEll"/>
+ <module name="LeftCurly"/>
+ <module name="RightCurly"/>
+ <module name="EmptyStatement"/>
+ <module name="ConstantName">
+ <property name="format" value="(^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$)|(^log$)"/>
+ </module>
+ <module name="LocalVariableName"/>
+ <module name="LocalFinalVariableName"/>
+ <module name="MemberName"/>
+ <module name="ClassTypeParameterName">
+ <property name="format" value="^[A-Z0-9]*$"/>
+ </module>
+ <module name="MethodTypeParameterName">
+ <property name="format" value="^[A-Z0-9]*$"/>
+ </module>
+ <module name="PackageName"/>
+ <module name="ParameterName"/>
+ <module name="StaticVariableName"/>
+ <module name="TypeName"/>
+ <module name="AvoidStarImport"/>
+
+
+ <!-- whitespace -->
+ <module name="GenericWhitespace"/>
+ <module name="NoWhitespaceBefore"/>
+ <module name="WhitespaceAfter"/>
+ <module name="NoWhitespaceAfter"/>
+ <module name="WhitespaceAround">
+ <property name="allowEmptyConstructors" value="true"/>
+ <property name="allowEmptyMethods" value="true"/>
+ </module>
+ <module name="Indentation"/>
+ <module name="MethodParamPad"/>
+ <module name="ParenPad"/>
+ <module name="TypecastParenPad"/>
+
+ <!-- locale-sensitive methods should specify locale -->
+ <module name="Regexp">
+ <!--<property name="format" value="\.to(Lower|Upper)Case\(\)"/>-->
+ <!--<property name="illegalPattern" value="true"/>-->
+ <property name="ignoreComments" value="true"/>
+ </module>
+ </module>
+</module>
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index e81938d..1e98b31 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+dropwizard-metrics (4.2.25-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Mon, 29 Jan 2024 01:12:41 -0000
+
dropwizard-metrics (3.2.6-1) unstable; urgency=medium
* Team upload.
diff --git a/debian/patches/drop-graphite-rabbitmq-support.patch b/debian/patches/drop-graphite-rabbitmq-support.patch
index ddb552a..15c99a8 100644
--- a/debian/patches/drop-graphite-rabbitmq-support.patch
+++ b/debian/patches/drop-graphite-rabbitmq-support.patch
@@ -4,9 +4,11 @@ Description: Do not build the Graphite RabbitMQ exporter
metrics-graphite to work, exclude GraphiteRabbitMQ from the build.
Last-Update: 2017-08-05
Forwarded: no
---- a/metrics-graphite/pom.xml
-+++ b/metrics-graphite/pom.xml
-@@ -34,4 +34,18 @@
+Index: dropwizard-metrics.git/metrics-graphite/pom.xml
+===================================================================
+--- dropwizard-metrics.git.orig/metrics-graphite/pom.xml
++++ dropwizard-metrics.git/metrics-graphite/pom.xml
+@@ -92,4 +92,18 @@
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/docs/pom.xml b/docs/pom.xml
index 205b0fc..1a94cda 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-parent</artifactId>
- <version>3.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>docs</artifactId>
@@ -14,7 +14,9 @@
<properties>
<jar.skipIfEmpty>true</jar.skipIfEmpty>
<mpir.skip>true</mpir.skip>
+ <maven.install.skip>true</maven.install.skip>
<maven.deploy.skip>true</maven.deploy.skip>
+ <javaModuleName>com.codahale.metrics.docs</javaModuleName>
</properties>
<build>
@@ -28,7 +30,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
- <version>1.9.1</version>
+ <version>3.5.0</version>
<executions>
<execution>
<id>parse-version</id>
@@ -42,6 +44,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
+ <version>3.3.1</version>
<executions>
<execution>
<id>process-resources</id>
@@ -70,6 +73,7 @@
<configuration>
<sourceDirectory>${project.build.directory}/source</sourceDirectory>
</configuration>
+ <version>2.10.0</version>
</plugin>
</plugins>
</reporting>
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..2a891db
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1 @@
+sphinx<7
diff --git a/docs/source/_themes/metrics/theme.conf b/docs/source/_themes/metrics/theme.conf
index d52f9a9..1b03e91 100644
--- a/docs/source/_themes/metrics/theme.conf
+++ b/docs/source/_themes/metrics/theme.conf
@@ -14,4 +14,4 @@ landing_logo = logo.png
landing_logo_width = 150px
github_page = https://github.com/yay
mailing_list = http://groups.google.com/yay
-apidocs = https://dropwizard.github.io/metrics/
+apidocs = https://www.javadoc.io/doc/io.dropwizard.metrics/metrics-core/
diff --git a/docs/source/about/contributors.rst b/docs/source/about/contributors.rst
index 0a62d1b..db8b2d4 100644
--- a/docs/source/about/contributors.rst
+++ b/docs/source/about/contributors.rst
@@ -6,58 +6,83 @@ Contributors
Many, many thanks to:
+* `Aaron Stockmeister <https://github.com/stockmaj>`_
* `Alan Woodward <https://github.com/romseygeek>`_
-* `Alexander Reelsen <https://github.com/spinscale>`_
+* `Aleksandr Podkutin <https://github.com/apodkutin>`_
* `Alex Lambert <https://github.com/alambert>`_
+* `Alexander Eyers-Taylor <https://github.com/alexet>`_
+* `Alexander Reelsen <https://github.com/spinscale>`_
+* `Alexey Nezhdanov <https://github.com/snakeru>`_
+* `Andreas Gebhardt <https://github.com/agebhar1>`_
+* `Andrew Fitzgerald <https://github.com/fitzoh>`_
+* `Andrey Rodionov <https://github.com/dernasherbrezon>`_
* `Anil V <https://github.com/avaitla>`_
* `Anthony Dahanne <https://github.com/anthonydahanne>`_
* `Antonin Stefanutti <https://github.com/astefanutti>`_
+* `apirom9 <https://github.com/apirom9>`_
* `Artem Prigoda <https://github.com/arteam>`_
-* `Bartosz KrasiΕski <https://github.com/krasinski>`_
+* `Ashley Sole <https://github.com/ashisamazin>`_
* `Bart Prokop <https://github.com/bartprokop>`_
+* `Bartosz KrasiΕski <https://github.com/krasinski>`_
* `Basil James Whitehouse III <https://github.com/basil3whitehouse>`_
-* `Benjamin Gehrels <https://github.com/BGehrels>`_
* `Ben Tatham <https://github.com/bentatham>`_
-* `Bogdan Storozhuk <https://github.com/storozhukBM>`_
+* `Benjamin Gehrels <https://github.com/BGehrels>`_
+* `Bohdan 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>`_
+* `Carter Kozak <https://github.com/carterkozak>`_
* `cburroughs <https://github.com/cburroughs>`_
* `ceetav <https://github.com/ceetav>`_
* `Charles Care <https://github.com/ccare>`_
* `Chris Birchall <https://github.com/cb372>`_
+* `Chris Rohr <https://github.com/chrisrohr>`_
* `Christopher Gray <https://github.com/chrisgray>`_
* `Christopher Swenson <https://github.com/swenson>`_
* `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/deverton>`_
-* `Daniel James <https://github.com/dwhjames>`_
* `Dan Revel <https://github.com/nopolabs>`_
+* `Daniel James <https://github.com/dwhjames>`_
+* `DarkJenum <https://github.com/DarkJenum>`_
+* `David Hatanian <https://github.com/dhatanian>`_
* `David M. Karr <https://github.com/davidmichaelkarr>`_
+* `David Pursehouse <https://github.com/dpursehouse>`_
* `David Schlosnagle <https://github.com/schlosna>`_
* `David Sutherland <https://github.com/djsutho>`_
+* `Denny Abraham Cheriyan <https://github.com/dennyac>`_
* `Diwaker Gupta <https://github.com/diwakergupta>`_
* `Drew Stephens <https://github.com/dinomite>`_
* `Eduard Martinescu <https://github.com/Arvoreen>`_
* `Edwin Shin <https://github.com/eddies>`_
* `Erik van Oosten <https://github.com/erikvanoosten>`_
+* `Erlend Hamnaberg <https://github.com/hamnis>`_
* `Evan Jones <https://github.com/evanj>`_
+* `Fabien Renaud <https://github.com/fabienrenaud>`_
* `Fabrizio Cannizzo <https://github.com/smartrics>`_
+* `Fokko Driesprong <https://github.com/Fokko>`_
* `François Beausoleil <https://github.com/francois>`_
+* `Fred Deschenes <https://github.com/FredDeschenes>`_
+* `g-fresh <https://github.com/g-fresh>`_
* `Gabor Arki <https://github.com/arkigabor>`_
* `George Spalding <https://github.com/georgespalding>`_
* `Gerolf Seitz <https://github.com/gseitz>`_
* `gilbode <https://github.com/gilbode>`_
+* `goraxe <https://github.com/goraxe>`_
* `Greg Bowyer <https://github.com/GregBowyer>`_
+* `Gregory Haase <https://github.com/ghaase>`_
+* `Guillermo Calvo <https://github.com/guillermocalvo>`_
* `Gunnar Ahlberg <https://github.com/gunnarahlberg>`_
* `Henri Tremblay <https://github.com/henri-tremblay>`_
+* `HervΓ© Boutemy <https://github.com/hboutemy>`_
+* `Himangi Saraogi <https://github.com/hsaraogi>`_
* `ho3rexqj <https://github.com/ho3rexqj>`_
* `Hussein Elsayed <https://github.com/husseincoder>`_
* `Ian Strachan <https://github.com/ianestrachan>`_
@@ -77,44 +102,70 @@ Many, many thanks to:
* `Jens Schauder <https://github.com/schauder>`_
* `Jesper Blomquist <https://github.com/jebl01>`_
* `Jesse Eichar <https://github.com/jesseeichar>`_
+* `jkytomaki <https://github.com/jkytomaki>`_
* `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 Karp <https://github.com/john-karp>`_
* `John Wang <https://github.com/javasoze>`_
+* `John Watson <https://github.com/jkwatson>`_
+* `John-John Tedro <https://github.com/udoprog>`_
+* `Jonathan Haber <https://github.com/jhaber>`_
* `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>`_
+* `JΓΆrg Fischer <https://github.com/g-fresh>`_
+* `Kasa <https://github.com/raskasa>`_
+* `KaseiFR <https://github.com/KaseiFR>`_
+* `Keir Lawson <https://github.com/keirlawson>`_
* `Kevin Clark <https://github.com/kevinclark>`_
+* `Kevin Herron <https://github.com/kevinherron>`_
* `Kevin Menard <https://github.com/nirvdrum>`_
* `Kevin Yeh <https://github.com/kyeah>`_
+* `keze <https://github.com/keze>`_
* `konnik <https://github.com/konnik>`_
+* `krasinski <https://github.com/krasinski>`_
* `Larry Shatzer, Jr. <https://github.com/larrys>`_
* `Luke Amdor <https://github.com/rubbish>`_
+* `Magnus Reftel <https://github.com/reftel>`_
* `Mahesh Tiyyagura <https://github.com/tmahesh>`_
+* `Marcin L <https://github.com/the-thing>`_
* `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>`_
+* `Mateusz Zakarczemny <https://github.com/Matzz>`_
* `Matheus Cabral <https://github.com/mcgois>`_
* `Matt Abrams <https://github.com/abramsm>`_
+* `Matt Veitas <https://github.com/mveitas>`_
* `Matthew Gilliard <https://github.com/mjg123>`_
* `Matthew O'Connor <https://github.com/oconnor0>`_
-* `Matt Veitas <https://github.com/mveitas>`_
+* `Matthias Wiedemann <https://github.com/mwiede>`_
+* `Michael Golahi <https://github.com/mgolahi>`_
+* `Michael Peyton Jones <https://github.com/michaelpj>`_
+* `Michael Vorburger <https://github.com/vorburger>`_
* `MichaΕ Minicki <https://github.com/martel>`_
* `Miikka Koskinen <https://github.com/miikka>`_
+* `Mike Gilbode <https://github.com/gilbode>`_
+* `Mike Minicki <https://github.com/martel>`_
+* `MΓ₯rten Gustafson <https://github.com/chids>`_
* `Neil Prosser <https://github.com/neilprosser>`_
* `Nick Babcock <https://github.com/nickbabcock>`_
* `Nick Telford <https://github.com/nicktelford>`_
+* `Nikolai Mazurkin <https://github.com/mazurkin>`_
* `Norbert Potocki <https://github.com/norbertpotocki>`_
* `Pablo Fernandez <https://github.com/fernandezpablo85>`_
* `Patryk Najda <https://github.com/patrox>`_
* `Paul Brown <https://github.com/prb>`_
* `Paul Doran <https://github.com/dorzey>`_
+* `Paul Oliver <https://github.com/puzza007>`_
* `Paul Sanwald <https://github.com/pcsanwald>`_
+* `Peter Steiner <https://github.com/pe-st>`_
+* `Philip Dakowitz <https://github.com/philmtd>`_
+* `Philip Helger <https://github.com/phax>`_
* `Philipp Hauer <https://github.com/phauer>`_
+* `Rahul Ravindran <https://github.com/rahulravindran0108>`_
* `Raman Gupta <https://github.com/rocketraman>`_
* `Realbot <https://github.com/realbot>`_
* `Robby Walker <https://github.com/robbywalker>`_
@@ -124,28 +175,41 @@ Many, many thanks to:
* `Ryan Tenney <https://github.com/ryantenney>`_
* `saadmufti <https://github.com/saadmufti>`_
* `Sam Perman <https://github.com/samperman>`_
+* `Sammy Chu <https://github.com/sammyhk>`_
* `Samy Dindane <https://github.com/Dinduks>`_
+* `Scott Leberknight <https://github.com/sleberknight>`_
* `Sean Laurent <https://github.com/organicveggie>`_
* `Sebastian LΓΆvdahl <https://github.com/slovdahl>`_
* `Sergey Nazarov <https://github.com/phearnot>`_
+* `Sergio Escalante <https://github.com/sergioescala>`_
+* `Shashank babu <https://github.com/shashank-babu>`_
* `Silvia MandalΓ <https://github.com/simad>`_
* `sofax <https://github.com/sofax>`_
+* `Stephen Souness <https://github.com/Sounie>`_
* `Steve Fosdal <https://github.com/sfosdal>`_
* `Steven Schlansker <https://github.com/stevenschlansker>`_
* `stockmaj <https://github.com/stockmaj>`_
* `Stuart Gunter <https://github.com/stuartgunter>`_
+* `Tamas Cservenak <https://github.com/cstamas>`_
* `Thomas Cashman <https://github.com/tomcashman>`_
+* `Tim Van Laer <https://github.com/timvlaer>`_
* `Tobias Bieniek <https://github.com/Turbo87>`_
* `Tobias Lidskog <https://github.com/tobli>`_
* `Tom Akehurst <https://github.com/tomakehurst>`_
+* `Tom Golden <https://github.com/TomRK1089>`_
+* `Tomas Celaya <https://github.com/tjcelaya>`_
* `Tomasz Guzik <https://github.com/tguzik>`_
* `Tomasz Nurkiewicz <https://github.com/nurkiewicz>`_
-* `Tom Golden <https://github.com/TomRK1089>`_
+* `tomayoola <https://github.com/tomayoola>`_
* `tvleminckx <https://github.com/tvleminckx>`_
+* `Ufuk Celebi <https://github.com/uce>`_
* `v-garki <https://github.com/v-garki>`_
+* `Vadym Pechenoha <https://github.com/pechenoha>`_
+* `Vasileios <https://github.com/vasilhsfoto>`_
* `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>`_
+* `Yuriy Badalyantc <https://github.com/LMnet>`_
* `Zach A. Thomas <https://github.com/zathomas>`_
diff --git a/docs/source/about/release-notes.rst b/docs/source/about/release-notes.rst
index 7d96638..d6a5423 100644
--- a/docs/source/about/release-notes.rst
+++ b/docs/source/about/release-notes.rst
@@ -4,6 +4,56 @@
Release Notes
#############
+Please refer to the `GitHub releases <https://github.com/dropwizard/metrics/releases>`_ for the latest releases of Dropwizard Metrics.
+
+.. _rel-4.0.0:
+
+v4.0.0: Dec 24 2017
+===================
+
+* Compiled and targeted JDK8
+* Support for running under JDK9 `#1236 <https://github.com/dropwizard/metrics/pull/1236>`_
+* Move JMX reporting to the ``metrics-jmx`` module
+* Add Bill of Materials for Metrics #1239 `#1239 <https://github.com/dropwizard/metrics/pull/1239>`_
+* Used Java 8 Time API for data formatting
+* Removed unnecessary reflection hacks for ``HealthCheckRegistry``
+* Removed internal ``LongAdder``
+* Removed internal ``ThreadLocalRandom``
+* Optimized generating random numbers
+* ``Timer.Context`` now implements ``AutoCloseable``
+* Upgrade Jetty integration to Jetty 9.4
+* Support tracking Jersey filters in Jersey resources `#1118 <https://github.com/dropwizard/metrics/pull/1239>`_
+* Add ``ResponseMetered`` annotation for Jersey resources `#1186 <https://github.com/dropwizard/metrics/pull/1186>`_
+* Add a method for timing non-throwing functions. `#1224 <https://github.com/dropwizard/metrics/pull/1224>`_
+* Unnecessary clear operation for ChunkedAssociativeArray `#1211 <https://github.com/dropwizard/metrics/pull/1211>`_
+* Add some common metric filters `#1210 <https://github.com/dropwizard/metrics/pull/1210>`_
+* Add possibility to subclass Timer.Context `#1226 <https://github.com/dropwizard/metrics/pull/1226>`_
+
+.. _rel-3.2.6:
+
+v3.2.6: Dec 24 2017
+===================
+
+* Jetty9: unhandled response should be counted as 404 and not 200 `#1232 <https://github.com/dropwizard/metrics/pull/1232>`_
+* Prevent NaN values when calculating mean `#1230 <https://github.com/dropwizard/metrics/pull/1230>`_
+* Avoid NaN values in WeightedSnapshot `#1233 <https://github.com/dropwizard/metrics/pull/1233>`_
+
+.. _rel-3.2.5:
+
+v3.2.5: Sep 15 2017
+===================
+
+* [InstrumentedScheduledExecutorService] Fix the scheduledFixedDelay to call the correct method `#1192 <https://github.com/dropwizard/metrics/pull/1192>`_
+
+.. _rel-3.2.4:
+
+v3.2.4: Aug 24 2017
+===================
+
+* Fix GraphiteReporter rate reporting `#1167 <https://github.com/dropwizard/metrics/pull/1167>`_
+* Remove non Jdk6 compatible letter from date pattern `#1163 <https://github.com/dropwizard/metrics/pull/1163>`_
+* Fix uncaught CancellationException when stopping reporter `#1170 <https://github.com/dropwizard/metrics/pull/1170>`_
+
.. _rel-3.2.3:
v3.2.3: Jun 28 2017
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 49c301b..9c5d462 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -41,7 +41,7 @@ master_doc = 'index'
# General information about the project.
project = u'Metrics'
-copyright = u'2010-2014, Coda Hale, Yammer Inc.'
+copyright = u'2010-2014, Coda Hale, Yammer Inc., 2014-2017 Dropwizard Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -107,7 +107,7 @@ html_theme_options = {
'landing_logo_width': u'200px',
'github_page': u'https://github.com/dropwizard/metrics',
'mailing_list': u'https://groups.google.com/forum/#!forum/metrics-user',
- 'apidocs': u'https://dropwizard.github.io/metrics/' + release + '/apidocs/'
+ 'apidocs': u'https://www.javadoc.io/doc/io.dropwizard.metrics/metrics-core/' + release + '/'
}
# Add any paths that contain custom themes here, relative to this directory.
diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst
index 9576aaa..2c3247b 100644
--- a/docs/source/getting-started.rst
+++ b/docs/source/getting-started.rst
@@ -138,7 +138,7 @@ To run
.. code-block:: sh
- mvn package exec:java -Dexec.mainClass=sample.First
+ mvn package exec:java -Dexec.mainClass=sample.GetStarted
.. _gs-registry:
@@ -264,13 +264,10 @@ duration.
private final Timer responses = metrics.timer(name(RequestHandler.class, "responses"));
public String handleRequest(Request request, Response response) {
- final Timer.Context context = responses.time();
- try {
+ try(final Timer.Context context = responses.time()) {
// etc;
return "OK";
- } finally {
- context.stop();
- }
+ } // catch and final logic goes here
}
This timer will measure the amount of time it takes to process each request in nanoseconds and
@@ -343,7 +340,20 @@ built-in thread deadlock detection to determine if any threads are deadlocked.
Reporting Via JMX
=================
-To report metrics via JMX:
+To report metrics via JMX, include the ``metrics-jmx`` module as a dependency:
+
+.. code-block:: xml
+
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jmx</artifactId>
+ <version>${metrics.version}</version>
+ </dependency>
+
+.. note::
+
+ Make sure you have a ``metrics.version`` property declared in your POM with the current version,
+ which is |release|.
.. code-block:: java
@@ -399,5 +409,4 @@ In addition to JMX and HTTP, Metrics also has reporters for the following output
* ``STDOUT``, using :ref:`ConsoleReporter <man-core-reporters-console>` from ``metrics-core``
* ``CSV`` files, using :ref:`CsvReporter <man-core-reporters-csv>` from ``metrics-core``
* SLF4J loggers, using :ref:`Slf4jReporter <man-core-reporters-slf4j>` from ``metrics-core``
-* Ganglia, using :ref:`GangliaReporter <manual-ganglia>` from ``metrics-ganglia``
* Graphite, using :ref:`GraphiteReporter <manual-graphite>` from ``metrics-graphite``
diff --git a/docs/source/index.rst b/docs/source/index.rst
index a253f71..3bf8d88 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -12,7 +12,7 @@ Metrics is a Java library which gives you unparalleled insight into what your co
**in your production environment.**
With modules for common libraries like **Jetty**, **Logback**, **Log4j**, **Apache HttpClient**,
-**Ehcache**, **JDBI**, **Jersey** and reporting backends like **Ganglia** and **Graphite**, Metrics
+**Ehcache**, **JDBI**, **Jersey** and reporting backends like **Graphite**, Metrics
provides you with full-stack visibility.
.. toctree::
diff --git a/docs/source/manual/caffeine.rst b/docs/source/manual/caffeine.rst
new file mode 100644
index 0000000..45f61d1
--- /dev/null
+++ b/docs/source/manual/caffeine.rst
@@ -0,0 +1,36 @@
+.. _manual-caffeine:
+
+#####################
+Instrumenting Caffeine
+#####################
+
+.. highlight:: text
+
+.. rubric:: The ``metrics-caffeine`` module provides ``MetricsStatsCounter``, a metrics listener for
+ Caffeine_ caches:
+
+.. _Caffeine: https://github.com/ben-manes/caffeine
+
+.. code-block:: java
+
+ LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
+ .recordStats(() -> new MetricsStatsCounter(registry, "cache"))
+ .build(key -> key);
+
+The listener publishes these metrics:
+
++---------------------------+----------------------------------------------------------------------+
+| ``hits`` | Number of times a requested item was found in the cache. |
++---------------------------+----------------------------------------------------------------------+
+| ``misses`` | Number of times a requested item was not found in the cache. |
++---------------------------+----------------------------------------------------------------------+
+| ``loads-success`` | Timer for successful loads into cache. |
++---------------------------+----------------------------------------------------------------------+
+| ``loads-failure`` | Timer for failed loads into cache. |
++---------------------------+----------------------------------------------------------------------+
+| ``evictions`` | Histogram of eviction weights . |
++---------------------------+----------------------------------------------------------------------+
+| ``evictions-weight`` | Total weight of evicted entries. |
++---------------------------+----------------------------------------------------------------------+
+| ``evictions.<CAUSE>`` | Histogram of eviction weights for each RemovalCause |
++---------------------------+----------------------------------------------------------------------+
diff --git a/docs/source/manual/collectd.rst b/docs/source/manual/collectd.rst
new file mode 100644
index 0000000..7612f8f
--- /dev/null
+++ b/docs/source/manual/collectd.rst
@@ -0,0 +1,21 @@
+.. _manual-collectd:
+
+#####################
+Reporting to Collectd
+#####################
+
+The ``metrics-collectd`` module provides ``CollectdReporter``, which allows your application to
+constantly stream metric values to a Collectd_ server:
+
+.. _Collectd: https://collectd.org/
+
+.. code-block:: java
+
+ final Sender sender = new Sender("collectd.example.com", 2007);
+ final CollectdReporter reporter = CollectdReporter.forRegistry(registry)
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .filter(MetricFilter.ALL)
+ .build(sender);
+ reporter.start(1, TimeUnit.MINUTES);
+
diff --git a/docs/source/manual/core.rst b/docs/source/manual/core.rst
index 4fb0f1b..8367cf9 100644
--- a/docs/source/manual/core.rst
+++ b/docs/source/manual/core.rst
@@ -181,7 +181,7 @@ returned by a search:
.. code-block:: java
- final Histogram resultCounts = registry.histogram(name(ProductDAO.class, "result-counts");
+ final Histogram resultCounts = registry.histogram(name(ProductDAO.class, "result-counts"));
resultCounts.update(results.size());
``Histogram`` metrics allow you to measure not just easy things like the min, mean, max, and
@@ -206,7 +206,7 @@ Metrics provides a number of different ``Reservoir`` implementations, each of wh
Uniform Reservoirs
------------------
-A histogram with a uniform reservoir produces quantiles which are valid for the entirely of the
+A histogram with a uniform reservoir produces quantiles which are valid for the entirety of the
histogram's lifetime. It will return a median value, for example, which is the median of all the
values the histogram has ever been updated with. It does this by using an algorithm called
`Vitter's R`__), which randomly selects values for the reservoir with linearly-decreasing
@@ -428,8 +428,6 @@ Metrics has other reporter implementations, too:
* :ref:`MetricsServlet <manual-servlets>` is a servlet which not only exposes your metrics as a JSON
object, but it also runs your health checks, performs thread dumps, and exposes valuable JVM-level
and OS-level information.
-* :ref:`GangliaReporter <manual-ganglia>` allows you to constantly stream metrics data to your
- Ganglia servers.
* :ref:`GraphiteReporter <manual-graphite>` allows you to constantly stream metrics data to your
Graphite servers.
diff --git a/docs/source/manual/ganglia.rst b/docs/source/manual/ganglia.rst
deleted file mode 100644
index ff5ac8c..0000000
--- a/docs/source/manual/ganglia.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-.. _manual-ganglia:
-
-####################
-Reporting to Ganglia
-####################
-
-The ``metrics-ganglia`` module provides ``GangliaReporter``, which allows your application to
-constantly stream metric values to a Ganglia_ server:
-
-.. _Ganglia: http://ganglia.sourceforge.net/
-
-.. code-block:: java
-
- final GMetric ganglia = new GMetric("ganglia.example.com", 8649, UDPAddressingMode.MULTICAST, 1);
- final GangliaReporter reporter = GangliaReporter.forRegistry(registry)
- .convertRatesTo(TimeUnit.SECONDS)
- .convertDurationsTo(TimeUnit.MILLISECONDS)
- .build(ganglia);
- reporter.start(1, TimeUnit.MINUTES);
diff --git a/docs/source/manual/index.rst b/docs/source/manual/index.rst
index a094801..273f716 100644
--- a/docs/source/manual/index.rst
+++ b/docs/source/manual/index.rst
@@ -14,7 +14,8 @@ User Manual
core
healthchecks
ehcache
- ganglia
+ caffeine
+ collectd
graphite
httpclient
jdbi
diff --git a/docs/source/manual/jdbi.rst b/docs/source/manual/jdbi.rst
index dd4a7da..8fa3074 100644
--- a/docs/source/manual/jdbi.rst
+++ b/docs/source/manual/jdbi.rst
@@ -4,7 +4,7 @@
Instrumenting JDBI
##################
-The ``metrics-jdbi`` module provides a ``TimingCollector`` implementation for JDBI_, an SQL
+The ``metrics-jdbi`` and ``metrics-jdbi3`` modules provide a ``TimingCollector`` implementation for JDBI_, an SQL
convenience library.
.. _JDBI: http://jdbi.org/
diff --git a/docs/source/manual/jersey.rst b/docs/source/manual/jersey.rst
index f88a151..1e47a13 100644
--- a/docs/source/manual/jersey.rst
+++ b/docs/source/manual/jersey.rst
@@ -1,44 +1,5 @@
.. _manual-jersey:
-########################
-Instrumenting Jersey 1.x
-########################
-
-The ``metrics-jersey`` module provides ``InstrumentedResourceMethodDispatchAdapter``, which allows
-you to instrument methods on your `Jersey 1.x`_ resource classes:
-
-.. _Jersey 1.x: https://jersey.java.net/documentation/1.18/index.html
-
-An instance of ``InstrumentedResourceMethodDispatchAdapter`` must be registered with your Jersey
-application's ``ResourceConfig`` as a singleton provider for this to work.
-
-.. code-block:: java
-
- public class ExampleApplication {
- private final DefaultResourceConfig config = new DefaultResourceConfig();
-
- public void init() {
- config.getSingletons().add(new InstrumentedResourceMethodDispatchAdapter(registry));
- config.getClasses().add(ExampleResource.class);
- }
- }
-
-
- @Path("/example")
- @Produces(MediaType.TEXT_PLAIN)
- public class ExampleResource {
- @GET
- @Timed
- public String show() {
- return "yay";
- }
- }
-
-The ``show`` method in the above example will have a timer attached to it, measuring the time spent
-in that method.
-
-Use of the ``@Metered`` and ``@ExceptionMetered`` annotations is also supported.
-
########################
Instrumenting Jersey 2.x
########################
@@ -50,7 +11,7 @@ which allows you to instrument methods on your `Jersey 2.x`_ resource classes:
The ``metrics-jersey2`` module provides ``InstrumentedResourceMethodApplicationListener``, which allows
you to instrument methods on your `Jersey 2.x`_ resource classes:
-.. _Jersey 2.x: https://jersey.java.net/documentation/latest/index.html
+.. _Jersey 2.x: https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/index.html
An instance of ``InstrumentedResourceMethodApplicationListener`` must be registered with your Jersey
application's ``ResourceConfig`` as a singleton provider for this to work.
@@ -76,9 +37,43 @@ application's ``ResourceConfig`` as a singleton provider for this to work.
public String show() {
return "yay";
}
+
+ @GET
+ @Metered(name = "fancyName")
+ @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) {
+ throw new IOException("AUGH");
+ }
+ return "fuh";
+ }
+
+ @GET
+ @ResponseMetered(level = ResponseMeteredLevel.ALL)
+ @Path("/response-metered")
+ public Response responseMetered(@QueryParam("invalid") @DefaultValue("false") boolean invalid) {
+ if (invalid) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+ return Response.ok().build();
+ }
}
-The ``show`` method in the above example will have a timer attached to it, measuring the time spent
-in that method.
+Supported Annotations
+=====================
+
+Every resource method or the class itself can be annotated with @Timed, @Metered, @ResponseMetered and @ExceptionMetered.
+If the annotation is placed on the class, it will apply to all its resource methods.
-Use of the ``@Metered`` and ``@ExceptionMetered`` annotations is also supported.
+* ``@Timed`` adds a timer and measures time spent in that method.
+* ``@Metered`` adds a meter and measures the rate at which the resource method is accessed.
+* ``@ResponseMetered`` adds meters and measures rate for response codes based on the selected level.
+* ``@ExceptionMetered`` adds a meter and measures how often the specified exception occurs when processing the resource.
+ If the ``cause`` is not specified, the default is ``Exception.class``.
diff --git a/docs/source/manual/jetty.rst b/docs/source/manual/jetty.rst
index 3ecd423..b69b6ea 100644
--- a/docs/source/manual/jetty.rst
+++ b/docs/source/manual/jetty.rst
@@ -4,12 +4,11 @@
Instrumenting Jetty
###################
-The ``metrics-jetty8`` (Jetty 8.0), ``metrics-jetty9-legacy`` (Jetty 9.0), and ``metrics-jetty9``
-(Jetty 9.1 and higher) modules provides a set of instrumented equivalents of Jetty_ classes:
+The ``metrics-jetty9`` (Jetty 9.3 and higher) modules provides a set of instrumented equivalents of Jetty_ classes:
``InstrumentedBlockingChannelConnector``, ``InstrumentedHandler``, ``InstrumentedQueuedThreadPool``,
``InstrumentedSelectChannelConnector``, and ``InstrumentedSocketConnector``.
-.. _Jetty: http://www.eclipse.org/jetty/
+.. _Jetty: https://www.eclipse.org/jetty/
The ``Connector`` implementations are simple, instrumented subclasses of the Jetty connector types
which measure connection duration, the rate of accepted connections, connections, disconnections,
diff --git a/docs/source/manual/json.rst b/docs/source/manual/json.rst
index a973c9a..83bd99f 100644
--- a/docs/source/manual/json.rst
+++ b/docs/source/manual/json.rst
@@ -6,7 +6,7 @@ JSON Support
Metrics comes with ``metrics-json``, which features two reusable modules for Jackson_.
-.. _Jackson: http://wiki.fasterxml.com/JacksonHome
+.. _Jackson: https://github.com/FasterXML/jackson
This allows for the serialization of all metric types and health checks to a standard,
easily-parsable JSON format.
diff --git a/docs/source/manual/jvm.rst b/docs/source/manual/jvm.rst
index 03f7b18..c887447 100644
--- a/docs/source/manual/jvm.rst
+++ b/docs/source/manual/jvm.rst
@@ -13,4 +13,4 @@ Supported metrics include:
* Memory usage for all memory pools, including off-heap memory
* Breakdown of thread states, including deadlocks
* File descriptor usage
-* Buffer pool sizes and utilization (Java 7 only)
+* Buffer pool sizes and utilization
diff --git a/docs/source/manual/log4j.rst b/docs/source/manual/log4j.rst
index 5c8863c..c64467d 100644
--- a/docs/source/manual/log4j.rst
+++ b/docs/source/manual/log4j.rst
@@ -4,23 +4,10 @@
Instrumenting Log4j
###################
-The ``metrics-log4j`` and ``metrics-log4j2`` modules provide ``InstrumentedAppender``, a Log4j ``Appender`` implementation
-(for log4j 1.x and log4j 2.x correspondingly) which records the rate of logged events by their logging level.
-
-
-You can add it to the root logger programmatically.
-
-For log4j 1.x:
-
-.. code-block:: java
-
- InstrumentedAppender appender = new InstrumentedAppender(registry);
- appender.activateOptions();
- LogManager.getRootLogger().addAppender(appender);
-
-
-For log4j 2.x:
+The ``metrics-log4j2`` module provide ``InstrumentedAppender``, a Log4j_ ``Appender`` implementation
+which records the rate of logged events by their logging level. You can add it to the root logger programmatically.
+.. _Log4j: https://logging.apache.org/log4j/
.. code-block:: java
Filter filter = null; // That's fine if we don't use filters; https://logging.apache.org/log4j/2.x/manual/filters.html
diff --git a/docs/source/manual/logback.rst b/docs/source/manual/logback.rst
index cf7ed71..8ebefdb 100644
--- a/docs/source/manual/logback.rst
+++ b/docs/source/manual/logback.rst
@@ -4,9 +4,11 @@
Instrumenting Logback
#####################
-The ``metrics-logback`` module provides ``InstrumentedAppender``, a Logback ``Appender``
+The ``metrics-logback`` module provides ``InstrumentedAppender``, a Logback_ ``Appender``
implementation which records the rate of logged events by their logging level.
+.. _Logback: https://logback.qos.ch/
+
You add it to the root logger programmatically:
.. code-block:: java
diff --git a/docs/source/manual/servlets.rst b/docs/source/manual/servlets.rst
index b7eea74..ca5c62f 100644
--- a/docs/source/manual/servlets.rst
+++ b/docs/source/manual/servlets.rst
@@ -11,15 +11,66 @@ The ``metrics-servlets`` module provides a handful of useful servlets:
HealthCheckServlet
==================
-``HealthCheckServlet`` responds to ``GET`` requests by running all the [health checks](#health-checks)
-and returning ``501 Not Implemented`` if no health checks are registered, ``200 OK`` if all pass, or
-``500 Internal Service Error`` if one or more fail. The results are returned as a human-readable
-``text/plain`` entity.
+``HealthCheckServlet`` responds to ``GET`` requests by running all the currently-registered
+[health checks](#health-checks). The results are returned as a human-readable JSON entity.
+
+HTTP Status Codes
+-----------------
+
+``HealthCheckServlet`` responds with one of the following status codes (depending on configuration).
+If reporting health via HTTP status is disabled, callers will have to introspect the JSON to
+determine application health.
+
+* ``501 Not Implemented``: If no health checks are registered
+* ``200 OK``: If all checks pass, or if ``httpStatusIndicator`` is set to ``"false"`` and one or more
+ health checks fail (see below for more information on this setting)
+* ``500 Internal Service Error``: If ``httpStatusIndicator`` is set to ``"true"`` and one or more
+ health checks fail (see below for more information on this setting)
+
+Configuration
+-------------
+
+``HealthCheckServlet`` supports the following configuration items.
+
+Servlet Context
+~~~~~~~~~~~~~~~
``HealthCheckServlet`` requires that the servlet context has a ``HealthCheckRegistry`` named
``com.codahale.metrics.servlets.HealthCheckServlet.registry``. You can subclass
-``MetricsServletContextListener``, which will add a specific ``HealthCheckRegistry`` to the servlet
-context.
+``HealthCheckServlet.ContextListener``, which will add a specific ``HealthCheckRegistry`` to the
+servlet context.
+
+An instance of ``ExecutorService`` can be provided via the servlet context using the name
+``com.codahale.metrics.servlets.HealthCheckServlet.executor``; by default, no thread pool is used to
+execute the health checks.
+
+An instance of ``HealthCheckFilter`` can be provided via the servlet context using the name
+``com.codahale.metrics.servlets.HealthCheckServlet.healthCheckFilter``; by default, no filtering is
+enabled. The filter is used to determine which health checks to include in the health status.
+
+An instance of ``ObjectMapper`` can be provided via the servlet context using the name
+``com.codahale.metrics.servlets.HealthCheckServlet.mapper``; if none is provided, a default instance
+will be used to convert the health check results to JSON.
+
+Initialization Parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``HealthCheckServlet`` supports the following initialization parameters:
+
+* ``com.codahale.metrics.servlets.HealthCheckServlet.httpStatusIndicator``: Provides the
+ default setting that determines whether the HTTP status code is used to determine whether the
+ application is healthy; if not provided, it defaults to ``"true"``
+
+Query Parameters
+~~~~~~~~~~~~~~~~
+
+``HealthCheckServlet`` supports the following query parameters:
+
+* ``httpStatusIndicator`` (``Boolean``): Determines whether the HTTP status code is used to
+ determine whether the application is healthy; if not provided, it defaults to the value from the
+ initialization parameter
+* ``pretty`` (``Boolean``): Indicates whether the JSON response should be formatted; if
+ ``"true"``, the JSON response will be formatted instead of condensed
.. _man-servlet-threaddump:
@@ -30,6 +81,21 @@ ThreadDumpServlet
threads in the JVM, their states, their stack traces, and the state of any locks they may be
waiting for.
+Configuration
+-------------
+
+``ThreadDumpServlet`` supports the following configuration items.
+
+Query Parameters
+~~~~~~~~~~~~~~~~
+
+``ThreadDumpServlet`` supports the following query parameters:
+
+* ``monitors`` (``Boolean``): Determines whether locked monitors are included; if not provided,
+ it defaults to ``"true"``
+* ``synchronizers`` (``Boolean``): Determines whether locked ownable synchronizers are included;
+ if not provided, it defaults to ``"true"``
+
.. _man-servlet-metrics:
MetricsServlet
@@ -37,13 +103,50 @@ MetricsServlet
``MetricsServlet`` exposes the state of the metrics in a particular registry as a JSON object.
+Configuration
+-------------
+
+``MetricsServlet`` supports the following configuration items.
+
+Servlet Context
+~~~~~~~~~~~~~~~
+
``MetricsServlet`` requires that the servlet context has a ``MetricRegistry`` named
``com.codahale.metrics.servlets.MetricsServlet.registry``. You can subclass
-``MetricsServletContextListener``, which will add a specific ``MetricRegistry`` to the servlet
+``MetricsServlet.ContextListener``, which will add a specific ``MetricRegistry`` to the servlet
context.
-``MetricsServlet`` also takes an initialization parameter, ``show-jvm-metrics``, which if ``"false"`` will
-disable the outputting of JVM-level information in the JSON object.
+An instance of ``MetricFilter`` can be provided via the servlet context using the name
+``com.codahale.metrics.servlets.MetricsServlet.metricFilter``; by default, no filtering is
+enabled. The filter is used to determine which metrics to include in the JSON output.
+
+Initialization Parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``MetricsServlet`` supports the following initialization parameters:
+
+* ``com.codahale.metrics.servlets.MetricsServlet.allowedOrigin``: Provides a value for the
+ response header ``Access-Control-Allow-Origin``; if no value is provided, the header is not used
+* ``com.codahale.metrics.servlets.MetricsServlet.jsonpCalblack``: Specifies a request parameter
+ name to use as the callback when returning the metrics as JSON-P; if no value is provided, the response is
+ returned as JSON. This also requires a query parameter with the same name as the value to enable a JSON-P
+ response.
+* ``com.codahale.metrics.servlets.MetricsServlet.rateUnit``: Provides a value for the
+ rate unit used for metrics output; if none is provided, the default is ``SECONDS`` (see ``TimeUnit`` for
+ acceptable values)
+* ``com.codahale.metrics.servlets.MetricsServlet.durationUnit``: Provides a value for the
+ duration unit used for metrics output; if none is provided, the default is ``SECONDS`` (see ``TimeUnit`` for
+ acceptable values)
+* ``com.codahale.metrics.servlets.MetricsServlet.showSamples``: Controls whether sample data is
+ included in the output for histograms and timers; if no value is provided, the sample data will be omitted.
+
+Query Parameters
+~~~~~~~~~~~~~~~~
+
+``MetricsServlet`` supports the following query parameters:
+
+* ``pretty`` (``Boolean``): Determines whether the results are formatted; if not provided, this
+ parameter defaults to ``"false"``.
.. _man-servlet-ping:
@@ -53,6 +156,32 @@ PingServlet
``PingServlet`` responds to ``GET`` requests with a ``text/plain``/``200 OK`` response of ``pong``. This is
useful for determining liveness for load balancers, etc.
+.. _man-servlet-cpu-profile:
+
+CpuProfileServlet
+=================
+
+``CpuProfileServlet`` responds to ``GET`` requests with a ``pprof/raw``/``200 OK`` response containing the
+results of CPU profiling.
+
+Configuration
+-------------
+
+``CpuProfileServlet`` supports the following configuration items.
+
+Query Parameters
+~~~~~~~~~~~~~~~~
+
+``CpuProfileServlet`` supports the following query parameters:
+
+* ``duration`` (``Integer``): Determines the amount of time in seconds for which the CPU
+ profiling will occur; the default is 10 seconds.
+* ``frequency`` (``Integer``)Determines the frequency in Hz at which the CPU
+ profiling sample; the default is 100 Hz (100 times per second).
+* ``state`` (``String``): Determines which threads will be profiled. If the value provided
+ is ``"blocked"``, only blocked threads will be profiled; otherwise, all runnable threads will be
+ profiled.
+
.. _man-servlet-admin:
AdminServlet
@@ -63,10 +192,13 @@ AdminServlet
* ``/``: an HTML admin menu with links to the following:
- * ``/healthcheck``: ``HealthCheckServlet``
* ``/metrics``: ``MetricsServlet``
+ * To change the URI, set the
* ``/ping``: ``PingServlet``
* ``/threads``: ``ThreadDumpServlet``
+ * ``/healthcheck``: ``HealthCheckServlet``
+ * ``/pprof``: ``CpuProfileServlet``
+ * There will be two links; one for the base profile and one for CPU contention
You will need to add your ``MetricRegistry`` and ``HealthCheckRegistry`` instances to the servlet
context as attributes named ``com.codahale.metrics.servlets.MetricsServlet.registry`` and
@@ -101,7 +233,8 @@ And by extending ``HealthCheckServlet.ContextListener`` for HealthCheckRegistry:
}
-Then you will need to register servlet context listeners either in you ``web.xml`` or annotating the class with ``@WebListener`` if you are in servlet 3.0 environment. In ``web.xml``:
+Then you will need to register servlet context listeners either in you ``web.xml`` or annotating the class
+with ``@WebListener`` if you are in servlet 3.0 environment. In ``web.xml``:
.. code-block:: xml
@@ -125,4 +258,29 @@ You will also need to register ``AdminServlet`` in ``web.xml``:
<url-pattern>/metrics/*</url-pattern>
</servlet-mapping>
-
+Configuration
+-------------
+
+``AdminServlet`` supports the following configuration items.
+
+Initialization Parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``AdminServlet`` supports the following initialization parameters:
+
+* ``metrics-enabled``: Determines whether the ``MetricsServlet`` is enabled and
+ routable; if ``"false"``, the servlet endpoint will not be available via this servlet
+* ``metrics-uri``: Specifies the URI for the ``MetricsServlet``; if omitted, the default
+ (``/metrics``) will be used
+* ``ping-enabled``: Determines whether the ``PingServlet`` is enabled and routable; if
+ ``"false"``, the servlet endpoint will not be available via this servlet
+* ``ping-uri``: Specifies the URI for the ``PingServlet``; if omitted, the default
+ (``/ping``) will be used
+* ``threads-enabled``: Determines whether the ``ThreadDumpServlet`` is enabled
+ and routable; if ``"false"``, the servlet endpoint will not be available via this servlet
+* ``threads-uri``: Specifies the URI for the ``ThreadDumpServlet``; if omitted, the default
+ (``/threads``) will be used
+* ``cpu-profile-enabled``: Determines whether the ``CpuProfileServlet`` is enabled and routable;
+ if ``"false"``, the servlet endpoints will not be available via this servlet
+* ``cpu-profile-uri``: Specifies the URIs for the ``CpuProfileServlet``; if omitted, the default
+ (``/pprof``) will be used
diff --git a/docs/source/manual/third-party.rst b/docs/source/manual/third-party.rst
index 0511ed1..46a8b1e 100644
--- a/docs/source/manual/third-party.rst
+++ b/docs/source/manual/third-party.rst
@@ -12,13 +12,14 @@ 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/>`_.
+* `jersey2-metrics <https://bitbucket.org/marshallpierce/jersey2-metrics>`_ provides integration with `Jersey 2 <https://eclipse-ee4j.github.io/jersey/>`_.
* `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-guice <https://github.com/palominolabs/metrics-guice>`_ provides integration with `Guice <https://github.com/google/guice>`_.
* `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-feign <https://github.com/mwiede/metrics-feign>`_ provides integration with `Feign <https://github.com/OpenFeign/feign>`_.
* `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.
@@ -38,15 +39,17 @@ 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-elasticsearch-reporter <https://github.com/elasticsearch/elasticsearch-metrics-reporter-java>`_ provides a reporter for `elasticsearch <https://www.elastic.co/>`_
* `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-influxdb <https://github.com/iZettle/dropwizard-metrics-influxdb>`_ provides a reporter for `InfluxDB <https://www.influxdata.com/>`_ with the Dropwizard framework integration.
+* `metrics-influxdb <https://github.com/kickstarter/dropwizard-influxdb-reporter>`_ provides a reporter for `InfluxDB <https://www.influxdata.com/>`_ 1.2+._
* `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/>`_
+* `dropwizard-metrics-newrelic <https://github.com/newrelic/dropwizard-metrics-newrelic>`_ Officially supported (by New Relic) exporter which sends data to `New Relic <http://newrelic.com/>`_ as dimensional metrics.
* `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/>`_.
@@ -54,8 +57,9 @@ Reporters
* `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>`_.
+* `sematext-metrics-reporter <https://github.com/sematext/sematext-metrics-reporter>`_ provides a reporter for `SPM <https://sematext.com/spm/>`_.
+* `metrics-jfr <https://github.com/poiu-de/metrics-jfr>`_ provides a reporter to publish event via `Java Flight Recorder <https://docs.oracle.com/en/java/java-components/jdk-mission-control/8/user-guide/using-jdk-flight-recorder.html>`_.
-Advansed metrics implementations
+Advanced 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 8de1f0f..8b07a78 100644
--- a/metrics-annotation/pom.xml
+++ b/metrics-annotation/pom.xml
@@ -5,9 +5,13 @@
<parent>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-parent</artifactId>
- <version>3.2.6</version>
+ <version>4.2.25</version>
</parent>
+ <properties>
+ <javaModuleName>com.codahale.metrics.annotation</javaModuleName>
+ </properties>
+
<artifactId>metrics-annotation</artifactId>
<name>Annotations for Metrics</name>
<packaging>bundle</packaging>
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/CachedGauge.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/CachedGauge.java
index 42b0827..9c0cd51 100644
--- a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/CachedGauge.java
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/CachedGauge.java
@@ -9,7 +9,7 @@ import java.util.concurrent.TimeUnit;
/**
* An annotation for marking a method as a gauge, which caches the result for a specified time.
*
- * <p/>
+ * <p>
* Given a method like this:
* <pre><code>
* {@literal @}CachedGauge(name = "queueSize", timeout = 30, timeoutUnit = TimeUnit.SECONDS)
@@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit;
* }
*
* </code></pre>
- * <p/>
+ * <p>
*
* A gauge for the defining class with the name queueSize will be created which uses the annotated method's
* return value as its value, and which caches the result for 30 seconds.
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Counted.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Counted.java
index 01c766e..3808395 100644
--- a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Counted.java
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Counted.java
@@ -10,7 +10,7 @@ import java.lang.annotation.Target;
/**
* An annotation for marking a method of an annotated object as counted.
*
- * <p/>
+ * <p>
* Given a method like this:
* <pre><code>
* {@literal @}Counted(name = "fancyName")
@@ -18,7 +18,7 @@ import java.lang.annotation.Target;
* return "Sir Captain " + name;
* }
* </code></pre>
- * <p/>
+ * <p>
* A counter for the defining class with the name {@code fancyName} will be created and each time the
* {@code #fancyName(String)} method is invoked, the counter will be marked.
*
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ExceptionMetered.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ExceptionMetered.java
index 71c489a..ddc69fc 100644
--- a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ExceptionMetered.java
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ExceptionMetered.java
@@ -9,7 +9,7 @@ import java.lang.annotation.Target;
/**
* An annotation for marking a method of an annotated object as metered.
- * <p/>
+ * <p>
* Given a method like this:
* <pre><code>
* {@literal @}ExceptionMetered(name = "fancyName", cause=IllegalArgumentException.class)
@@ -17,14 +17,14 @@ import java.lang.annotation.Target;
* return "Sir Captain " + name;
* }
* </code></pre>
- * <p/>
+ * <p>
* A meter for the defining class with the name {@code fancyName} will be created and each time the
* {@code #fancyName(String)} throws an exception of type {@code cause} (or a subclass), the meter
* will be marked.
- * <p/>
+ * <p>
* A name for the metric can be specified as an annotation parameter, otherwise, the metric will be
* named based on the method name.
- * <p/>
+ * <p>
* For instance, given a declaration of
* <pre><code>
* {@literal @}ExceptionMetered
@@ -32,7 +32,7 @@ import java.lang.annotation.Target;
* return "Sir Captain " + name;
* }
* </code></pre>
- * <p/>
+ * <p>
* A meter named {@code fancyName.exceptions} will be created and marked every time an exception is
* thrown.
*/
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Gauge.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Gauge.java
index 2849704..35819b4 100644
--- a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Gauge.java
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Gauge.java
@@ -7,7 +7,7 @@ import java.lang.annotation.Target;
/**
* An annotation for marking a method of an annotated object as a gauge.
- * <p/>
+ * <p>
* Given a method like this:
* <pre><code>
* {@literal @}Gauge(name = "queueSize")
@@ -15,7 +15,7 @@ import java.lang.annotation.Target;
* return queue.size;
* }
* </code></pre>
- * <p/>
+ * <p>
* A gauge for the defining class with the name {@code queueSize} will be created which uses the
* annotated method's return value as its value.
*/
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Metered.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Metered.java
index d074020..f8b5db0 100644
--- a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Metered.java
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Metered.java
@@ -9,7 +9,7 @@ import java.lang.annotation.Target;
/**
* An annotation for marking a method of an annotated object as metered.
- * <p/>
+ * <p>
* Given a method like this:
* <pre><code>
* {@literal @}Metered(name = "fancyName")
@@ -17,7 +17,7 @@ import java.lang.annotation.Target;
* return "Sir Captain " + name;
* }
* </code></pre>
- * <p/>
+ * <p>
* A meter for the defining class with the name {@code fancyName} will be created and each time the
* {@code #fancyName(String)} method is invoked, the meter will be marked.
*/
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Metric.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Metric.java
index 374b72f..ca7eaff 100755
--- a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Metric.java
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Metric.java
@@ -1,18 +1,3 @@
-/**
- * Copyright (C) 2012 Ryan W Tenney (ryan@10e.us)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
package com.codahale.metrics.annotation;
import java.lang.annotation.ElementType;
@@ -23,13 +8,13 @@ import java.lang.annotation.Target;
/**
* An annotation requesting that a metric be injected or registered.
*
- * <p/>
+ * <p>
* Given a field like this:
* <pre><code>
* {@literal @}Metric
* public Histogram histogram;
* </code></pre>
- * <p/>
+ * <p>
* A meter of the field's type will be created and injected into managed objects.
* It will be up to the user to interact with the metric. This annotation
* can be used on fields of type Meter, Timer, Counter, and Histogram.
@@ -41,7 +26,7 @@ import java.lang.annotation.Target;
* {@literal @}Metric
* public Histogram uniformHistogram = new Histogram(new UniformReservoir());
* </code></pre>
- * </p>
+ * <p>
*
* @since 3.1
*/
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ResponseMetered.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ResponseMetered.java
new file mode 100644
index 0000000..ea5d876
--- /dev/null
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ResponseMetered.java
@@ -0,0 +1,45 @@
+package com.codahale.metrics.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for marking a method of an annotated object as metered.
+ * <p>
+ * Given a method like this:
+ * <pre><code>
+ * {@literal @}ResponseMetered(name = "fancyName", level = ResponseMeteredLevel.ALL)
+ * public String fancyName(String name) {
+ * return "Sir Captain " + name;
+ * }
+ * </code></pre>
+ * <p>
+ * Meters for the defining class with the name {@code fancyName} will be created for response codes
+ * based on the ResponseMeteredLevel selected. Each time the {@code #fancyName(String)} method is invoked,
+ * the appropriate response meter will be marked.
+ */
+@Inherited
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+public @interface ResponseMetered {
+ /**
+ * @return The name of the meter.
+ */
+ String name() default "";
+
+ /**
+ * @return If {@code true}, use the given name as an absolute name. If {@code false}, use the given name
+ * relative to the annotated class. When annotating a class, this must be {@code false}.
+ */
+ boolean absolute() default false;
+
+ /**
+ * @return the ResponseMeteredLevel which decides which response code meters are marked.
+ */
+ ResponseMeteredLevel level() default ResponseMeteredLevel.COARSE;
+}
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ResponseMeteredLevel.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ResponseMeteredLevel.java
new file mode 100644
index 0000000..d17d0e5
--- /dev/null
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/ResponseMeteredLevel.java
@@ -0,0 +1,23 @@
+package com.codahale.metrics.annotation;
+
+/**
+ * {@link ResponseMeteredLevel} is a parameter for the {@link ResponseMetered} annotation.
+ * The constants of this enumerated type decide what meters are included when a class
+ * or method is annotated with the {@link ResponseMetered} annotation.
+ */
+public enum ResponseMeteredLevel {
+ /**
+ * Include meters for 1xx/2xx/3xx/4xx/5xx responses
+ */
+ COARSE,
+
+ /**
+ * Include meters for every response code (200, 201, 303, 304, 401, 404, 501, etc.)
+ */
+ DETAILED,
+
+ /**
+ * Include meters for every response code in addition to top level 1xx/2xx/3xx/4xx/5xx responses
+ */
+ ALL;
+}
diff --git a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Timed.java b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Timed.java
index 10b56ee..bf8cd47 100644
--- a/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Timed.java
+++ b/metrics-annotation/src/main/java/com/codahale/metrics/annotation/Timed.java
@@ -9,7 +9,7 @@ import java.lang.annotation.Target;
/**
* An annotation for marking a method of an annotated object as timed.
- * <p/>
+ * <p>
* Given a method like this:
* <pre><code>
* {@literal @}Timed(name = "fancyName")
@@ -17,7 +17,7 @@ import java.lang.annotation.Target;
* return "Sir Captain " + name;
* }
* </code></pre>
- * <p/>
+ * <p>
* A timer for the defining class with the name {@code fancyName} will be created and each time the
* {@code #fancyName(String)} method is invoked, the method's execution will be timed.
*/
diff --git a/metrics-benchmarks/findbugs-exclude.xml b/metrics-benchmarks/findbugs-exclude.xml
deleted file mode 100644
index 44f6116..0000000
--- a/metrics-benchmarks/findbugs-exclude.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<FindBugsFilter>
- <Match>
- <Package name="com.codahale.metrics.benchmarks.generated" />
- </Match>
- <Match>
- <Package name="org.openjdk.jmh.infra.generated" />
- </Match>
-</FindBugsFilter>
diff --git a/metrics-benchmarks/pom.xml b/metrics-benchmarks/pom.xml
index 12c86e9..332801d 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-benchmarks</artifactId>
@@ -14,41 +14,58 @@
A development module for performance benchmarks of Metrics classes.
</description>
+ <properties>
+ <jmh.version>1.37</jmh.version>
+ <javaModuleName>com.codahale.metrics.benchmarks</javaModuleName>
+ <jar.skipIfEmpty>true</jar.skipIfEmpty>
+ <maven.install.skip>true</maven.install.skip>
+ <maven.deploy.skip>true</maven.deploy.skip>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <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>
+ <version>${jmh.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
- <!-- don't deploy this -->
<groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-deploy-plugin</artifactId>
- <version>2.7</version>
+ <artifactId>maven-compiler-plugin</artifactId>
<configuration>
- <skip>true</skip>
+ <annotationProcessorPaths combine.children="append">
+ <path>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <version>${jmh.version}</version>
+ </path>
+ </annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<!-- generate an uber .jar for standalone benchmarking -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
- <version>2.2</version>
+ <version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
@@ -66,14 +83,6 @@
</execution>
</executions>
</plugin>
- <plugin>
- <!-- exclude jmh generated classes designed to trick jvm like "unused fields" padding -->
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <configuration>
- <excludeFilterFile>findbugs-exclude.xml</excludeFilterFile>
- </configuration>
- </plugin>
</plugins>
</build>
</project>
diff --git a/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/CachedGaugeBenchmark.java b/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/CachedGaugeBenchmark.java
new file mode 100644
index 0000000..ae1d5cd
--- /dev/null
+++ b/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/CachedGaugeBenchmark.java
@@ -0,0 +1,46 @@
+package com.codahale.metrics.benchmarks;
+
+import com.codahale.metrics.CachedGauge;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+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 java.util.concurrent.TimeUnit;
+
+@State(Scope.Benchmark)
+public class CachedGaugeBenchmark {
+
+ private CachedGauge<Integer> cachedGauge = new CachedGauge<Integer>(100, TimeUnit.MILLISECONDS) {
+ @Override
+ protected Integer loadValue() {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Thread was interrupted", e);
+ }
+ return 12345;
+ }
+ };
+
+ @Benchmark
+ public void perfGetValue(Blackhole blackhole) {
+ blackhole.consume(cachedGauge.getValue());
+ }
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(".*" + CachedGaugeBenchmark.class.getSimpleName() + ".*")
+ .warmupIterations(3)
+ .measurementIterations(5)
+ .threads(4)
+ .forks(1)
+ .build();
+
+ new Runner(opt).run();
+ }
+}
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 5d2e788..b7c6801 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,6 +1,8 @@
package com.codahale.metrics.benchmarks;
import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.LockFreeExponentiallyDecayingReservoir;
+import com.codahale.metrics.Reservoir;
import com.codahale.metrics.SlidingTimeWindowArrayReservoir;
import com.codahale.metrics.SlidingTimeWindowReservoir;
import com.codahale.metrics.SlidingWindowReservoir;
@@ -23,6 +25,7 @@ public class ReservoirBenchmark {
private final UniformReservoir uniform = new UniformReservoir();
private final ExponentiallyDecayingReservoir exponential = new ExponentiallyDecayingReservoir();
+ private final Reservoir lockFreeExponential = LockFreeExponentiallyDecayingReservoir.builder().build();
private final SlidingWindowReservoir sliding = new SlidingWindowReservoir(1000);
private final SlidingTimeWindowReservoir slidingTime = new SlidingTimeWindowReservoir(200, TimeUnit.MILLISECONDS);
private final SlidingTimeWindowArrayReservoir arrTime = new SlidingTimeWindowArrayReservoir(200, TimeUnit.MILLISECONDS);
@@ -60,6 +63,12 @@ public class ReservoirBenchmark {
return slidingTime;
}
+ @Benchmark
+ public Object perfLockFreeExponentiallyDecayingReservoir() {
+ lockFreeExponential.update(nextValue);
+ return lockFreeExponential;
+ }
+
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(".*" + ReservoirBenchmark.class.getSimpleName() + ".*")
diff --git a/metrics-bom/pom.xml b/metrics-bom/pom.xml
new file mode 100644
index 0000000..0ab914d
--- /dev/null
+++ b/metrics-bom/pom.xml
@@ -0,0 +1,191 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-bom</artifactId>
+ <name>Metrics BOM</name>
+ <packaging>pom</packaging>
+ <description>Bill of Materials for Metrics</description>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-caffeine</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-caffeine3</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-collectd</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-ehcache</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-graphite</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-healthchecks</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-httpclient</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-httpclient5</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-httpasyncclient</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jakarta-servlet</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jakarta-servlet6</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jakarta-servlets</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jcache</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jdbi</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jdbi3</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jersey2</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jersey3</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jersey31</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jetty9</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jetty10</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jetty11</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jetty12</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jetty12-ee10</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jmx</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-json</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jvm</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-log4j2</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-logback</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-logback13</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-logback14</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-servlet</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-servlets</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ </dependencies>
+ </dependencyManagement>
+</project>
diff --git a/metrics-caffeine/pom.xml b/metrics-caffeine/pom.xml
new file mode 100644
index 0000000..a719829
--- /dev/null
+++ b/metrics-caffeine/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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-caffeine</artifactId>
+ <name>Metrics Integration for Caffeine 2.x</name>
+ <packaging>bundle</packaging>
+ <description>
+ Metrics Integration for Caffeine 2.x.
+ </description>
+
+ <properties>
+ <javaModuleName>com.codahale.metrics.caffeine</javaModuleName>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ <version>2.9.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ <version>3.42.0</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-caffeine/src/main/java/com/codahale/metrics/caffeine/MetricsStatsCounter.java b/metrics-caffeine/src/main/java/com/codahale/metrics/caffeine/MetricsStatsCounter.java
new file mode 100644
index 0000000..4056fe6
--- /dev/null
+++ b/metrics-caffeine/src/main/java/com/codahale/metrics/caffeine/MetricsStatsCounter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2016 Ben Manes. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.codahale.metrics.caffeine;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.EnumMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.github.benmanes.caffeine.cache.RemovalCause;
+import com.github.benmanes.caffeine.cache.stats.CacheStats;
+import com.github.benmanes.caffeine.cache.stats.StatsCounter;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+/**
+ * A {@link StatsCounter} instrumented with Dropwizard Metrics.
+ *
+ * @author ben.manes@gmail.com (Ben Manes)
+ * @author John Karp
+ */
+public final class MetricsStatsCounter implements StatsCounter {
+ private final Counter hitCount;
+ private final Counter missCount;
+ private final Timer loadSuccess;
+ private final Timer loadFailure;
+ private final Histogram evictions;
+ private final Counter evictionWeight;
+ private final EnumMap<RemovalCause, Histogram> evictionsWithCause;
+
+ // for implementing snapshot()
+ private final LongAdder totalLoadTime = new LongAdder();
+
+ /**
+ * Constructs an instance for use by a single cache.
+ *
+ * @param registry the registry of metric instances
+ * @param metricsPrefix the prefix name for the metrics
+ */
+ public MetricsStatsCounter(MetricRegistry registry, String metricsPrefix) {
+ requireNonNull(metricsPrefix);
+ hitCount = registry.counter(MetricRegistry.name(metricsPrefix, "hits"));
+ missCount = registry.counter(MetricRegistry.name(metricsPrefix, "misses"));
+ loadSuccess = registry.timer(MetricRegistry.name(metricsPrefix, "loads-success"));
+ loadFailure = registry.timer(MetricRegistry.name(metricsPrefix, "loads-failure"));
+ evictions = registry.histogram(MetricRegistry.name(metricsPrefix, "evictions"));
+ evictionWeight = registry.counter(MetricRegistry.name(metricsPrefix, "evictions-weight"));
+
+ evictionsWithCause = new EnumMap<>(RemovalCause.class);
+ for (RemovalCause cause : RemovalCause.values()) {
+ evictionsWithCause.put(
+ cause,
+ registry.histogram(MetricRegistry.name(metricsPrefix, "evictions", cause.name())));
+ }
+ }
+
+ @Override
+ public void recordHits(int count) {
+ hitCount.inc(count);
+ }
+
+ @Override
+ public void recordMisses(int count) {
+ missCount.inc(count);
+ }
+
+ @Override
+ public void recordLoadSuccess(long loadTime) {
+ loadSuccess.update(loadTime, TimeUnit.NANOSECONDS);
+ totalLoadTime.add(loadTime);
+ }
+
+ @Override
+ public void recordLoadFailure(long loadTime) {
+ loadFailure.update(loadTime, TimeUnit.NANOSECONDS);
+ totalLoadTime.add(loadTime);
+ }
+
+ // @Override -- Caffeine 2.x
+ @Deprecated
+ @SuppressWarnings("deprecation")
+ public void recordEviction() {
+ // This method is scheduled for removal in version 3.0 in favor of recordEviction(weight)
+ recordEviction(1);
+ }
+
+ // @Override -- Caffeine 2.x
+ @Deprecated
+ @SuppressWarnings("deprecation")
+ public void recordEviction(int weight) {
+ evictions.update(weight);
+ evictionWeight.inc(weight);
+ }
+
+ @Override
+ public void recordEviction(@NonNegative int weight, RemovalCause cause) {
+ evictionsWithCause.get(cause).update(weight);
+ evictionWeight.inc(weight);
+ }
+
+ @Override
+ public CacheStats snapshot() {
+ return CacheStats.of(
+ hitCount.getCount(),
+ missCount.getCount(),
+ loadSuccess.getCount(),
+ loadFailure.getCount(),
+ totalLoadTime.sum(),
+ evictions.getCount(),
+ evictionWeight.getCount());
+ }
+
+ @Override
+ public String toString() {
+ return snapshot().toString();
+ }
+}
diff --git a/metrics-caffeine/src/test/java/com/codahale/metrics/caffeine/MetricsStatsCounterTest.java b/metrics-caffeine/src/test/java/com/codahale/metrics/caffeine/MetricsStatsCounterTest.java
new file mode 100644
index 0000000..2547df6
--- /dev/null
+++ b/metrics-caffeine/src/test/java/com/codahale/metrics/caffeine/MetricsStatsCounterTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 Ben Manes. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.codahale.metrics.caffeine;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.codahale.metrics.MetricRegistry;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import com.github.benmanes.caffeine.cache.RemovalCause;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * An example of exporting stats to Dropwizard Metrics (http://metrics.dropwizard.io).
+ *
+ * @author ben.manes@gmail.com (Ben Manes)
+ * @author John Karp
+ */
+public final class MetricsStatsCounterTest {
+
+ private static final String PREFIX = "foo";
+
+ private MetricsStatsCounter stats;
+ private MetricRegistry registry;
+
+ @Before
+ public void setUp() {
+ registry = new MetricRegistry();
+ stats = new MetricsStatsCounter(registry, PREFIX);
+ }
+
+ @Test
+ public void basicUsage() {
+ LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
+ .recordStats(() -> new MetricsStatsCounter(registry, PREFIX))
+ .build(key -> key);
+
+ // Perform application work
+ for (int i = 0; i < 4; i++) {
+ cache.get(1);
+ }
+
+ assertEquals(3L, cache.stats().hitCount());
+ assertEquals(3L, registry.counter(PREFIX + ".hits").getCount());
+ }
+
+ @Test
+ public void hit() {
+ stats.recordHits(2);
+ assertThat(registry.counter(PREFIX + ".hits").getCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void miss() {
+ stats.recordMisses(2);
+ assertThat(registry.counter(PREFIX + ".misses").getCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void loadSuccess() {
+ stats.recordLoadSuccess(256);
+ assertThat(registry.timer(PREFIX + ".loads-success").getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void loadFailure() {
+ stats.recordLoadFailure(256);
+ assertThat(registry.timer(PREFIX + ".loads-failure").getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void eviction() {
+ stats.recordEviction();
+ assertThat(registry.histogram(PREFIX + ".evictions").getCount()).isEqualTo(1);
+ assertThat(registry.counter(PREFIX + ".evictions-weight").getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void evictionWithWeight() {
+ stats.recordEviction(3);
+ assertThat(registry.histogram(PREFIX + ".evictions").getCount()).isEqualTo(1);
+ assertThat(registry.counter(PREFIX + ".evictions-weight").getCount()).isEqualTo(3);
+ }
+
+ @Test
+ public void evictionWithCause() {
+ // With JUnit 5, this would be better done with @ParameterizedTest + @EnumSource
+ for (RemovalCause cause : RemovalCause.values()) {
+ stats.recordEviction(3, cause);
+ assertThat(registry.histogram(PREFIX + ".evictions." + cause.name()).getCount()).isEqualTo(1);
+ }
+ }
+}
diff --git a/metrics-caffeine3/pom.xml b/metrics-caffeine3/pom.xml
new file mode 100644
index 0000000..a554d82
--- /dev/null
+++ b/metrics-caffeine3/pom.xml
@@ -0,0 +1,73 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-caffeine3</artifactId>
+ <name>Metrics Integration for Caffeine 3.x</name>
+ <packaging>bundle</packaging>
+ <description>
+ Metrics Integration for Caffeine 3.x.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.caffeine3</javaModuleName>
+
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ <version>3.1.8</version>
+ </dependency>
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ <version>3.42.0</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-caffeine3/src/main/java/io/dropwizard/metrics/caffeine3/MetricsStatsCounter.java b/metrics-caffeine3/src/main/java/io/dropwizard/metrics/caffeine3/MetricsStatsCounter.java
new file mode 100644
index 0000000..ef37bbd
--- /dev/null
+++ b/metrics-caffeine3/src/main/java/io/dropwizard/metrics/caffeine3/MetricsStatsCounter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016 Ben Manes. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.dropwizard.metrics.caffeine3;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.EnumMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.github.benmanes.caffeine.cache.RemovalCause;
+import com.github.benmanes.caffeine.cache.stats.CacheStats;
+import com.github.benmanes.caffeine.cache.stats.StatsCounter;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+/**
+ * A {@link StatsCounter} instrumented with Dropwizard Metrics.
+ *
+ * @author ben.manes@gmail.com (Ben Manes)
+ * @author John Karp
+ */
+public final class MetricsStatsCounter implements StatsCounter {
+ private final Counter hitCount;
+ private final Counter missCount;
+ private final Timer loadSuccess;
+ private final Timer loadFailure;
+ private final Counter evictionWeight;
+ private final EnumMap<RemovalCause, Histogram> evictionsWithCause;
+
+ // for implementing snapshot()
+ private final LongAdder totalLoadTime = new LongAdder();
+
+ /**
+ * Constructs an instance for use by a single cache.
+ *
+ * @param registry the registry of metric instances
+ * @param metricsPrefix the prefix name for the metrics
+ */
+ public MetricsStatsCounter(MetricRegistry registry, String metricsPrefix) {
+ requireNonNull(metricsPrefix);
+ hitCount = registry.counter(MetricRegistry.name(metricsPrefix, "hits"));
+ missCount = registry.counter(MetricRegistry.name(metricsPrefix, "misses"));
+ loadSuccess = registry.timer(MetricRegistry.name(metricsPrefix, "loads-success"));
+ loadFailure = registry.timer(MetricRegistry.name(metricsPrefix, "loads-failure"));
+ evictionWeight = registry.counter(MetricRegistry.name(metricsPrefix, "evictions-weight"));
+
+ evictionsWithCause = new EnumMap<>(RemovalCause.class);
+ for (RemovalCause cause : RemovalCause.values()) {
+ evictionsWithCause.put(
+ cause,
+ registry.histogram(MetricRegistry.name(metricsPrefix, "evictions", cause.name())));
+ }
+ }
+
+ @Override
+ public void recordHits(int count) {
+ hitCount.inc(count);
+ }
+
+ @Override
+ public void recordMisses(int count) {
+ missCount.inc(count);
+ }
+
+ @Override
+ public void recordLoadSuccess(long loadTime) {
+ loadSuccess.update(loadTime, TimeUnit.NANOSECONDS);
+ totalLoadTime.add(loadTime);
+ }
+
+ @Override
+ public void recordLoadFailure(long loadTime) {
+ loadFailure.update(loadTime, TimeUnit.NANOSECONDS);
+ totalLoadTime.add(loadTime);
+ }
+
+ @Override
+ public void recordEviction(@NonNegative int weight, RemovalCause cause) {
+ evictionsWithCause.get(cause).update(weight);
+ evictionWeight.inc(weight);
+ }
+
+ @Override
+ public CacheStats snapshot() {
+ return CacheStats.of(
+ hitCount.getCount(),
+ missCount.getCount(),
+ loadSuccess.getCount(),
+ loadFailure.getCount(),
+ totalLoadTime.sum(),
+ evictionsWithCause.values().stream().mapToLong(Histogram::getCount).sum(),
+ evictionWeight.getCount());
+ }
+
+ @Override
+ public String toString() {
+ return snapshot().toString();
+ }
+}
diff --git a/metrics-caffeine3/src/test/java/io/dropwizard/metrics/caffeine3/MetricsStatsCounterTest.java b/metrics-caffeine3/src/test/java/io/dropwizard/metrics/caffeine3/MetricsStatsCounterTest.java
new file mode 100644
index 0000000..4f412db
--- /dev/null
+++ b/metrics-caffeine3/src/test/java/io/dropwizard/metrics/caffeine3/MetricsStatsCounterTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 Ben Manes. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.dropwizard.metrics.caffeine3;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.codahale.metrics.MetricRegistry;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import com.github.benmanes.caffeine.cache.RemovalCause;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * An example of exporting stats to <a href="https://metrics.dropwizard.io">Dropwizard Metrics</a>.
+ *
+ * @author ben.manes@gmail.com (Ben Manes)
+ * @author John Karp
+ */
+public final class MetricsStatsCounterTest {
+
+ private static final String PREFIX = "foo";
+
+ private MetricsStatsCounter stats;
+ private MetricRegistry registry;
+
+ @Before
+ public void setUp() {
+ registry = new MetricRegistry();
+ stats = new MetricsStatsCounter(registry, PREFIX);
+ }
+
+ @Test
+ public void basicUsage() {
+ LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
+ .recordStats(() -> new MetricsStatsCounter(registry, PREFIX))
+ .build(key -> key);
+
+ // Perform application work
+ for (int i = 0; i < 4; i++) {
+ Integer unused = cache.get(1);
+ }
+
+ assertEquals(3L, cache.stats().hitCount());
+ assertEquals(3L, registry.counter(PREFIX + ".hits").getCount());
+ }
+
+ @Test
+ public void hit() {
+ stats.recordHits(2);
+ assertThat(registry.counter(PREFIX + ".hits").getCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void miss() {
+ stats.recordMisses(2);
+ assertThat(registry.counter(PREFIX + ".misses").getCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void loadSuccess() {
+ stats.recordLoadSuccess(256);
+ assertThat(registry.timer(PREFIX + ".loads-success").getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void loadFailure() {
+ stats.recordLoadFailure(256);
+ assertThat(registry.timer(PREFIX + ".loads-failure").getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void evictionWithCause() {
+ // With JUnit 5, this would be better done with @ParameterizedTest + @EnumSource
+ for (RemovalCause cause : RemovalCause.values()) {
+ stats.recordEviction(3, cause);
+ assertThat(registry.histogram(PREFIX + ".evictions." + cause.name()).getCount()).isEqualTo(1);
+ }
+ }
+}
diff --git a/metrics-collectd/pom.xml b/metrics-collectd/pom.xml
new file mode 100644
index 0000000..f89713f
--- /dev/null
+++ b/metrics-collectd/pom.xml
@@ -0,0 +1,73 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-collectd</artifactId>
+ <name>Metrics Integration for Collectd</name>
+ <packaging>bundle</packaging>
+ <description>
+ A reporter for Metrics which announces measurements to Collectd.
+ </description>
+
+ <properties>
+ <javaModuleName>com.codahale.metrics.collectd</javaModuleName>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.hardis.collectd</groupId>
+ <artifactId>jcollectd</artifactId>
+ <version>1.0.3</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-collectd/src/main/java/com/codahale/metrics/collectd/CollectdReporter.java b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/CollectdReporter.java
new file mode 100644
index 0000000..5665f39
--- /dev/null
+++ b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/CollectdReporter.java
@@ -0,0 +1,337 @@
+package com.codahale.metrics.collectd;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricAttribute;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.ScheduledReporter;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+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 static com.codahale.metrics.MetricAttribute.COUNT;
+import static com.codahale.metrics.MetricAttribute.M15_RATE;
+import static com.codahale.metrics.MetricAttribute.M1_RATE;
+import static com.codahale.metrics.MetricAttribute.M5_RATE;
+import static com.codahale.metrics.MetricAttribute.MAX;
+import static com.codahale.metrics.MetricAttribute.MEAN;
+import static com.codahale.metrics.MetricAttribute.MEAN_RATE;
+import static com.codahale.metrics.MetricAttribute.MIN;
+import static com.codahale.metrics.MetricAttribute.P50;
+import static com.codahale.metrics.MetricAttribute.P75;
+import static com.codahale.metrics.MetricAttribute.P95;
+import static com.codahale.metrics.MetricAttribute.P98;
+import static com.codahale.metrics.MetricAttribute.P99;
+import static com.codahale.metrics.MetricAttribute.P999;
+import static com.codahale.metrics.MetricAttribute.STDDEV;
+
+/**
+ * A reporter which publishes metric values to a Collectd server.
+ *
+ * @see <a href="https://collectd.org">collectd β The system statistics
+ * collection daemon</a>
+ */
+public class CollectdReporter extends ScheduledReporter {
+
+ /**
+ * Returns a builder for the specified registry.
+ * <p>
+ * The default settings are:
+ * <ul>
+ * <li>hostName: InetAddress.getLocalHost().getHostName()</li>
+ * <li>executor: default executor created by {@code ScheduledReporter}</li>
+ * <li>shutdownExecutorOnStop: true</li>
+ * <li>clock: Clock.defaultClock()</li>
+ * <li>rateUnit: TimeUnit.SECONDS</li>
+ * <li>durationUnit: TimeUnit.MILLISECONDS</li>
+ * <li>filter: MetricFilter.ALL</li>
+ * <li>securityLevel: NONE</li>
+ * <li>username: ""</li>
+ * <li>password: ""</li>
+ * </ul>
+ */
+ public static Builder forRegistry(MetricRegistry registry) {
+ return new Builder(registry);
+ }
+
+ public static class Builder {
+
+ private final MetricRegistry registry;
+ private String hostName;
+ private ScheduledExecutorService executor;
+ private boolean shutdownExecutorOnStop = true;
+ private Clock clock = Clock.defaultClock();
+ private TimeUnit rateUnit = TimeUnit.SECONDS;
+ private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
+ private MetricFilter filter = MetricFilter.ALL;
+ private SecurityLevel securityLevel = SecurityLevel.NONE;
+ private String username = "";
+ private String password = "";
+ private Set<MetricAttribute> disabledMetricAttributes = Collections.emptySet();
+ private int maxLength = Sanitize.DEFAULT_MAX_LENGTH;
+
+ private Builder(MetricRegistry registry) {
+ this.registry = registry;
+ }
+
+ public Builder withHostName(String hostName) {
+ this.hostName = hostName;
+ return this;
+ }
+
+ public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
+ this.shutdownExecutorOnStop = shutdownExecutorOnStop;
+ return this;
+ }
+
+ public Builder scheduleOn(ScheduledExecutorService executor) {
+ this.executor = executor;
+ return this;
+ }
+
+ public Builder withClock(Clock clock) {
+ this.clock = clock;
+ return this;
+ }
+
+ public Builder convertRatesTo(TimeUnit rateUnit) {
+ this.rateUnit = rateUnit;
+ return this;
+ }
+
+ public Builder convertDurationsTo(TimeUnit durationUnit) {
+ this.durationUnit = durationUnit;
+ return this;
+ }
+
+ public Builder filter(MetricFilter filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ public Builder withUsername(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public Builder withPassword(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public Builder withSecurityLevel(SecurityLevel securityLevel) {
+ this.securityLevel = securityLevel;
+ return this;
+ }
+
+ public Builder disabledMetricAttributes(Set<MetricAttribute> attributes) {
+ this.disabledMetricAttributes = attributes;
+ return this;
+ }
+
+ public Builder withMaxLength(int maxLength) {
+ this.maxLength = maxLength;
+ return this;
+ }
+
+ public CollectdReporter build(Sender sender) {
+ if (securityLevel != SecurityLevel.NONE) {
+ if (username.isEmpty()) {
+ throw new IllegalArgumentException("username is required for securityLevel: " + securityLevel);
+ }
+ if (password.isEmpty()) {
+ throw new IllegalArgumentException("password is required for securityLevel: " + securityLevel);
+ }
+ }
+ return new CollectdReporter(registry,
+ hostName, sender,
+ executor, shutdownExecutorOnStop,
+ clock, rateUnit, durationUnit,
+ filter, disabledMetricAttributes,
+ username, password, securityLevel, new Sanitize(maxLength));
+ }
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(CollectdReporter.class);
+ private static final String REPORTER_NAME = "collectd-reporter";
+ private static final String FALLBACK_HOST_NAME = "localhost";
+ private static final String COLLECTD_TYPE_GAUGE = "gauge";
+
+ private String hostName;
+ private final Sender sender;
+ private final Clock clock;
+ private long period;
+ private final PacketWriter writer;
+ private final Sanitize sanitize;
+
+ private CollectdReporter(MetricRegistry registry,
+ String hostname, Sender sender,
+ ScheduledExecutorService executor, boolean shutdownExecutorOnStop,
+ Clock clock, TimeUnit rateUnit, TimeUnit durationUnit,
+ MetricFilter filter, Set<MetricAttribute> disabledMetricAttributes,
+ String username, String password,
+ SecurityLevel securityLevel, Sanitize sanitize) {
+ super(registry, REPORTER_NAME, filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,
+ disabledMetricAttributes);
+ this.hostName = (hostname != null) ? hostname : resolveHostName();
+ this.sender = sender;
+ this.clock = clock;
+ this.sanitize = sanitize;
+ writer = new PacketWriter(sender, username, password, securityLevel);
+ }
+
+ private String resolveHostName() {
+ try {
+ return InetAddress.getLocalHost().getHostName();
+ } catch (Exception e) {
+ LOG.error("Failed to lookup local host name: {}", e.getMessage(), e);
+ return FALLBACK_HOST_NAME;
+ }
+ }
+
+ @Override
+ public void start(long period, TimeUnit unit) {
+ this.period = period;
+ super.start(period, unit);
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters,
+ SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers) {
+ MetaData.Builder metaData = new MetaData.Builder(sanitize, hostName, clock.getTime() / 1000, period)
+ .type(COLLECTD_TYPE_GAUGE);
+ try {
+ connect(sender);
+ for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
+ serializeGauge(metaData.plugin(entry.getKey()), entry.getValue());
+ }
+ for (Map.Entry<String, Counter> entry : counters.entrySet()) {
+ serializeCounter(metaData.plugin(entry.getKey()), entry.getValue());
+ }
+ for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
+ serializeHistogram(metaData.plugin(entry.getKey()), entry.getValue());
+ }
+ for (Map.Entry<String, Meter> entry : meters.entrySet()) {
+ serializeMeter(metaData.plugin(entry.getKey()), entry.getValue());
+ }
+ for (Map.Entry<String, Timer> entry : timers.entrySet()) {
+ serializeTimer(metaData.plugin(entry.getKey()), entry.getValue());
+ }
+ } catch (IOException e) {
+ LOG.warn("Unable to report to Collectd", e);
+ } finally {
+ disconnect(sender);
+ }
+ }
+
+ private void connect(Sender sender) throws IOException {
+ if (!sender.isConnected()) {
+ sender.connect();
+ }
+ }
+
+ private void disconnect(Sender sender) {
+ try {
+ sender.disconnect();
+ } catch (Exception e) {
+ LOG.warn("Error disconnecting from Collectd", e);
+ }
+ }
+
+ private void writeValue(MetaData.Builder metaData, MetricAttribute attribute, Number value) {
+ if (!getDisabledMetricAttributes().contains(attribute)) {
+ write(metaData.typeInstance(attribute.getCode()).get(), value);
+ }
+ }
+
+ private void writeRate(MetaData.Builder metaData, MetricAttribute attribute, double rate) {
+ writeValue(metaData, attribute, convertRate(rate));
+ }
+
+ private void writeDuration(MetaData.Builder metaData, MetricAttribute attribute, double duration) {
+ writeValue(metaData, attribute, convertDuration(duration));
+ }
+
+ private void write(MetaData metaData, Number value) {
+ try {
+ writer.write(metaData, value);
+ } catch (RuntimeException e) {
+ LOG.warn("Failed to process metric '" + metaData.getPlugin() + "': " + e.getMessage());
+ } catch (IOException e) {
+ LOG.error("Failed to send metric to collectd", e);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void serializeGauge(MetaData.Builder metaData, Gauge metric) {
+ if (metric.getValue() instanceof Number) {
+ write(metaData.typeInstance("value").get(), (Number) metric.getValue());
+ } else if (metric.getValue() instanceof Boolean) {
+ write(metaData.typeInstance("value").get(), ((Boolean) metric.getValue()) ? 1 : 0);
+ } else {
+ LOG.warn("Failed to process metric '{}'. Unsupported gauge of type: {} ", metaData.get().getPlugin(),
+ metric.getValue().getClass().getName());
+ }
+ }
+
+ private void serializeMeter(MetaData.Builder metaData, Meter metric) {
+ writeValue(metaData, COUNT, (double) metric.getCount());
+ writeRate(metaData, M1_RATE, metric.getOneMinuteRate());
+ writeRate(metaData, M5_RATE, metric.getFiveMinuteRate());
+ writeRate(metaData, M15_RATE, metric.getFifteenMinuteRate());
+ writeRate(metaData, MEAN_RATE, metric.getMeanRate());
+ }
+
+ private void serializeCounter(MetaData.Builder metaData, Counter metric) {
+ writeValue(metaData, COUNT, (double) metric.getCount());
+ }
+
+ private void serializeHistogram(MetaData.Builder metaData, Histogram metric) {
+ final Snapshot snapshot = metric.getSnapshot();
+ writeValue(metaData, COUNT, (double) metric.getCount());
+ writeValue(metaData, MAX, (double) snapshot.getMax());
+ writeValue(metaData, MEAN, snapshot.getMean());
+ writeValue(metaData, MIN, (double) snapshot.getMin());
+ writeValue(metaData, STDDEV, snapshot.getStdDev());
+ writeValue(metaData, P50, snapshot.getMedian());
+ writeValue(metaData, P75, snapshot.get75thPercentile());
+ writeValue(metaData, P95, snapshot.get95thPercentile());
+ writeValue(metaData, P98, snapshot.get98thPercentile());
+ writeValue(metaData, P99, snapshot.get99thPercentile());
+ writeValue(metaData, P999, snapshot.get999thPercentile());
+ }
+
+ private void serializeTimer(MetaData.Builder metaData, Timer metric) {
+ final Snapshot snapshot = metric.getSnapshot();
+ writeValue(metaData, COUNT, (double) metric.getCount());
+ writeDuration(metaData, MAX, (double) snapshot.getMax());
+ writeDuration(metaData, MEAN, snapshot.getMean());
+ writeDuration(metaData, MIN, (double) snapshot.getMin());
+ writeDuration(metaData, STDDEV, snapshot.getStdDev());
+ writeDuration(metaData, P50, snapshot.getMedian());
+ writeDuration(metaData, P75, snapshot.get75thPercentile());
+ writeDuration(metaData, P95, snapshot.get95thPercentile());
+ writeDuration(metaData, P98, snapshot.get98thPercentile());
+ writeDuration(metaData, P99, snapshot.get99thPercentile());
+ writeDuration(metaData, P999, snapshot.get999thPercentile());
+ writeRate(metaData, M1_RATE, metric.getOneMinuteRate());
+ writeRate(metaData, M5_RATE, metric.getFiveMinuteRate());
+ writeRate(metaData, M15_RATE, metric.getFifteenMinuteRate());
+ writeRate(metaData, MEAN_RATE, metric.getMeanRate());
+ }
+}
diff --git a/metrics-collectd/src/main/java/com/codahale/metrics/collectd/MetaData.java b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/MetaData.java
new file mode 100644
index 0000000..8f7e239
--- /dev/null
+++ b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/MetaData.java
@@ -0,0 +1,98 @@
+package com.codahale.metrics.collectd;
+
+class MetaData {
+
+ private final String host;
+ private final String plugin;
+ private final String pluginInstance;
+ private final String type;
+ private final String typeInstance;
+ private final long timestamp;
+ private final long period;
+
+ MetaData(String host, String plugin, String pluginInstance, String type, String typeInstance,
+ long timestamp, long period) {
+ this.host = host;
+ this.plugin = plugin;
+ this.pluginInstance = pluginInstance;
+ this.type = type;
+ this.typeInstance = typeInstance;
+ this.timestamp = timestamp;
+ this.period = period;
+ }
+
+ String getHost() {
+ return host;
+ }
+
+ String getPlugin() {
+ return plugin;
+ }
+
+ String getPluginInstance() {
+ return pluginInstance;
+ }
+
+ String getType() {
+ return type;
+ }
+
+ String getTypeInstance() {
+ return typeInstance;
+ }
+
+ long getTimestamp() {
+ return timestamp;
+ }
+
+ long getPeriod() {
+ return period;
+ }
+
+ static class Builder {
+
+ private String host;
+ private String plugin;
+ private String pluginInstance;
+ private String type;
+ private String typeInstance;
+ private long timestamp;
+ private long period;
+ private Sanitize sanitize;
+
+ Builder(String host, long timestamp, long duration) {
+ this(new Sanitize(Sanitize.DEFAULT_MAX_LENGTH), host, timestamp, duration);
+ }
+
+ Builder(Sanitize sanitize, String host, long timestamp, long duration) {
+ this.sanitize = sanitize;
+ this.host = sanitize.instanceName(host);
+ this.timestamp = timestamp;
+ period = duration;
+ }
+
+ Builder plugin(String name) {
+ plugin = sanitize.name(name);
+ return this;
+ }
+
+ Builder pluginInstance(String name) {
+ pluginInstance = sanitize.instanceName(name);
+ return this;
+ }
+
+ Builder type(String name) {
+ type = sanitize.name(name);
+ return this;
+ }
+
+ Builder typeInstance(String name) {
+ typeInstance = sanitize.instanceName(name);
+ return this;
+ }
+
+ MetaData get() {
+ return new MetaData(host, plugin, pluginInstance, type, typeInstance, timestamp, period);
+ }
+ }
+}
diff --git a/metrics-collectd/src/main/java/com/codahale/metrics/collectd/PacketWriter.java b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/PacketWriter.java
new file mode 100644
index 0000000..a19efe0
--- /dev/null
+++ b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/PacketWriter.java
@@ -0,0 +1,275 @@
+package com.codahale.metrics.collectd;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+class PacketWriter {
+
+ private static final int TYPE_HOST = 0;
+ private static final int TYPE_TIME = 1;
+ private static final int TYPE_PLUGIN = 2;
+ private static final int TYPE_PLUGIN_INSTANCE = 3;
+ private static final int TYPE_TYPE = 4;
+ private static final int TYPE_TYPE_INSTANCE = 5;
+ private static final int TYPE_VALUES = 6;
+ private static final int TYPE_INTERVAL = 7;
+ private static final int TYPE_SIGN_SHA256 = 0x0200;
+ private static final int TYPE_ENCR_AES256 = 0x0210;
+
+ private static final int UINT16_LEN = 2;
+ private static final int UINT32_LEN = UINT16_LEN * 2;
+ private static final int UINT64_LEN = UINT32_LEN * 2;
+ private static final int HEADER_LEN = UINT16_LEN * 2;
+ private static final int BUFFER_SIZE = 1024;
+
+ private static final int VALUE_COUNT_LEN = UINT16_LEN;
+ private static final int NUMBER_LEN = HEADER_LEN + UINT64_LEN;
+ private static final int SIGNATURE_LEN = 36; // 2b Type + 2b Length + 32b Hash
+ private static final int ENCRYPT_DATA_LEN = 22; // 16b IV + 2b Type + 2b Length + 2b Username length
+ private static final int IV_LENGTH = 16;
+ private static final int SHA1_LENGTH = 20;
+
+ private static final int VALUE_LEN = 9;
+ private static final byte DATA_TYPE_GAUGE = (byte) 1;
+ private static final byte NULL = (byte) '\0';
+ private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+ private static final String AES_CYPHER = "AES_256/OFB/NoPadding";
+ private static final String AES = "AES";
+ private static final String SHA_256_ALGORITHM = "SHA-256";
+ private static final String SHA_1_ALGORITHM = "SHA1";
+
+ private final Sender sender;
+
+ private final SecurityLevel securityLevel;
+ private final byte[] username;
+ private final byte[] password;
+
+ PacketWriter(Sender sender, String username, String password, SecurityLevel securityLevel) {
+ this.sender = sender;
+ this.securityLevel = securityLevel;
+ this.username = username != null ? username.getBytes(StandardCharsets.UTF_8) : null;
+ this.password = password != null ? password.getBytes(StandardCharsets.UTF_8) : null;
+ }
+
+ void write(MetaData metaData, Number... values) throws BufferOverflowException, IOException {
+ final ByteBuffer packet = ByteBuffer.allocate(BUFFER_SIZE);
+ write(packet, metaData);
+ write(packet, values);
+ packet.flip();
+
+ switch (securityLevel) {
+ case NONE:
+ sender.send(packet);
+ break;
+ case SIGN:
+ sender.send(signPacket(packet));
+ break;
+ case ENCRYPT:
+ sender.send(encryptPacket(packet));
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported security level: " + securityLevel);
+ }
+ }
+
+
+ private void write(ByteBuffer buffer, MetaData metaData) {
+ writeString(buffer, TYPE_HOST, metaData.getHost());
+ writeNumber(buffer, TYPE_TIME, metaData.getTimestamp());
+ writeString(buffer, TYPE_PLUGIN, metaData.getPlugin());
+ writeString(buffer, TYPE_PLUGIN_INSTANCE, metaData.getPluginInstance());
+ writeString(buffer, TYPE_TYPE, metaData.getType());
+ writeString(buffer, TYPE_TYPE_INSTANCE, metaData.getTypeInstance());
+ writeNumber(buffer, TYPE_INTERVAL, metaData.getPeriod());
+ }
+
+ private void write(ByteBuffer buffer, Number... values) {
+ final int numValues = values.length;
+ final int length = HEADER_LEN + VALUE_COUNT_LEN + numValues * VALUE_LEN;
+ writeHeader(buffer, TYPE_VALUES, length);
+ buffer.putShort((short) numValues);
+ buffer.put(nCopies(numValues, DATA_TYPE_GAUGE));
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ for (Number value : values) {
+ buffer.putDouble(value.doubleValue());
+ }
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ }
+
+ private byte[] nCopies(int n, byte value) {
+ final byte[] array = new byte[n];
+ Arrays.fill(array, value);
+ return array;
+ }
+
+ private void writeString(ByteBuffer buffer, int type, String val) {
+ if (val == null || val.length() == 0) {
+ return;
+ }
+ int len = HEADER_LEN + val.length() + 1;
+ writeHeader(buffer, type, len);
+ buffer.put(val.getBytes(StandardCharsets.US_ASCII)).put(NULL);
+ }
+
+ private void writeNumber(ByteBuffer buffer, int type, long val) {
+ writeHeader(buffer, type, NUMBER_LEN);
+ buffer.putLong(val);
+ }
+
+ private void writeHeader(ByteBuffer buffer, int type, int len) {
+ buffer.putShort((short) type);
+ buffer.putShort((short) len);
+ }
+
+ /**
+ * Signs the provided packet, so a CollectD server can verify that its authenticity.
+ * Wire format:
+ * <pre>
+ * +-------------------------------+-------------------------------+
+ * ! Type (0x0200) ! Length !
+ * +-------------------------------+-------------------------------+
+ * ! Signature (SHA2(username + packet)) \
+ * +-------------------------------+-------------------------------+
+ * ! Username ! Packet \
+ * +---------------------------------------------------------------+
+ * </pre>
+ *
+ * @see <a href="https://collectd.org/wiki/index.php/Binary_protocol#Signature_part">
+ * Binary protocol - CollectD | Signature part</a>
+ */
+ private ByteBuffer signPacket(ByteBuffer packet) {
+ final byte[] signature = sign(password, (ByteBuffer) ByteBuffer.allocate(packet.remaining() + username.length)
+ .put(username)
+ .put(packet)
+ .flip());
+ return (ByteBuffer) ByteBuffer.allocate(BUFFER_SIZE)
+ .putShort((short) TYPE_SIGN_SHA256)
+ .putShort((short) (username.length + SIGNATURE_LEN))
+ .put(signature)
+ .put(username)
+ .put((ByteBuffer) packet.flip())
+ .flip();
+ }
+
+ /**
+ * Encrypts the provided packet, so it's can't be eavesdropped during a transfer
+ * to a CollectD server. Wire format:
+ * <pre>
+ * +---------------------------------+-------------------------------+
+ * ! Type (0x0210) ! Length !
+ * +---------------------------------+-------------------------------+
+ * ! Username length in bytes ! Username \
+ * +-----------------------------------------------------------------+
+ * ! Initialization Vector (IV) ! \
+ * +---------------------------------+-------------------------------+
+ * ! Encrypted bytes (AES (SHA1(packet) + packet)) \
+ * +---------------------------------+-------------------------------+
+ * </pre>
+ *
+ * @see <a href="https://collectd.org/wiki/index.php/Binary_protocol#Encrypted_part">
+ * Binary protocol - CollectD | Encrypted part</a>
+ */
+ private ByteBuffer encryptPacket(ByteBuffer packet) {
+ final ByteBuffer payload = (ByteBuffer) ByteBuffer.allocate(SHA1_LENGTH + packet.remaining())
+ .put(sha1(packet))
+ .put((ByteBuffer) packet.flip())
+ .flip();
+ final EncryptionResult er = encrypt(password, payload);
+ return (ByteBuffer) ByteBuffer.allocate(BUFFER_SIZE)
+ .putShort((short) TYPE_ENCR_AES256)
+ .putShort((short) (ENCRYPT_DATA_LEN + username.length + er.output.remaining()))
+ .putShort((short) username.length)
+ .put(username)
+ .put(er.iv)
+ .put(er.output)
+ .flip();
+ }
+
+ private static byte[] sign(byte[] secret, ByteBuffer input) {
+ final Mac mac;
+ try {
+ mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+ mac.init(new SecretKeySpec(secret, HMAC_SHA256_ALGORITHM));
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ mac.update(input);
+ return mac.doFinal();
+ }
+
+ private static EncryptionResult encrypt(byte[] password, ByteBuffer input) {
+ final Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(AES_CYPHER);
+ cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sha256(password), AES));
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ final byte[] iv;
+ try {
+ iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
+ } catch (InvalidParameterSpecException e) {
+ throw new RuntimeException(e);
+ }
+ if (iv.length != IV_LENGTH) {
+ throw new IllegalStateException("Bad initialization vector");
+ }
+ final ByteBuffer output = ByteBuffer.allocate(input.remaining() * 2);
+ try {
+ cipher.doFinal(input, output);
+ } catch (ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
+ throw new RuntimeException(e);
+ }
+ return new EncryptionResult(iv, (ByteBuffer) output.flip());
+ }
+
+ private static byte[] sha256(byte[] input) {
+ try {
+ return MessageDigest.getInstance(SHA_256_ALGORITHM).digest(input);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static byte[] sha1(ByteBuffer input) {
+ try {
+ final MessageDigest digest = MessageDigest.getInstance(SHA_1_ALGORITHM);
+ digest.update(input);
+ final byte[] output = digest.digest();
+ if (output.length != SHA1_LENGTH) {
+ throw new IllegalStateException("Bad SHA1 hash");
+ }
+ return output;
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class EncryptionResult {
+
+ private final byte[] iv;
+ private final ByteBuffer output;
+
+ private EncryptionResult(byte[] iv, ByteBuffer output) {
+ this.iv = iv;
+ this.output = output;
+ }
+ }
+
+}
diff --git a/metrics-collectd/src/main/java/com/codahale/metrics/collectd/Sanitize.java b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/Sanitize.java
new file mode 100644
index 0000000..156d1f6
--- /dev/null
+++ b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/Sanitize.java
@@ -0,0 +1,46 @@
+package com.codahale.metrics.collectd;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @see <a href="https://collectd.org/wiki/index.php/Naming_schema>Collectd naming schema</a>
+ */
+class Sanitize {
+
+ static final int DEFAULT_MAX_LENGTH = 63;
+
+ private static final char DASH = '-';
+ private static final char SLASH = '/';
+ private static final char NULL = '\0';
+ private static final char UNDERSCORE = '_';
+
+ private static final List<Character> INSTANCE_RESERVED = Arrays.asList(SLASH, NULL);
+ private static final List<Character> NAME_RESERVED = Arrays.asList(DASH, SLASH, NULL);
+
+ private final int maxLength;
+
+ Sanitize(int maxLength) {
+ this.maxLength = maxLength;
+ }
+
+ String name(String name) {
+ return sanitize(name, NAME_RESERVED);
+ }
+
+ String instanceName(String instanceName) {
+ return sanitize(instanceName, INSTANCE_RESERVED);
+ }
+
+ private String sanitize(String string, List<Character> reservedChars) {
+ final StringBuilder buffer = new StringBuilder(string.length());
+ final int len = Math.min(string.length(), maxLength);
+ for (int i = 0; i < len; i++) {
+ final char c = string.charAt(i);
+ final boolean legal = ((int) c) < 128 && !reservedChars.contains(c);
+ buffer.append(legal ? c : UNDERSCORE);
+ }
+ return buffer.toString();
+ }
+
+}
diff --git a/metrics-collectd/src/main/java/com/codahale/metrics/collectd/SecurityConfiguration.java b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/SecurityConfiguration.java
new file mode 100644
index 0000000..0bb1a27
--- /dev/null
+++ b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/SecurityConfiguration.java
@@ -0,0 +1,30 @@
+package com.codahale.metrics.collectd;
+
+public class SecurityConfiguration {
+
+ private final byte[] username;
+ private final byte[] password;
+ private final SecurityLevel securityLevel;
+
+ public SecurityConfiguration(byte[] username, byte[] password, SecurityLevel securityLevel) {
+ this.username = username;
+ this.password = password;
+ this.securityLevel = securityLevel;
+ }
+
+ public static SecurityConfiguration none() {
+ return new SecurityConfiguration(null, null, SecurityLevel.NONE);
+ }
+
+ public byte[] getUsername() {
+ return username;
+ }
+
+ public byte[] getPassword() {
+ return password;
+ }
+
+ public SecurityLevel getSecurityLevel() {
+ return securityLevel;
+ }
+}
diff --git a/metrics-collectd/src/main/java/com/codahale/metrics/collectd/SecurityLevel.java b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/SecurityLevel.java
new file mode 100644
index 0000000..df819c0
--- /dev/null
+++ b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/SecurityLevel.java
@@ -0,0 +1,8 @@
+package com.codahale.metrics.collectd;
+
+public enum SecurityLevel {
+
+ NONE,
+ SIGN,
+ ENCRYPT
+}
diff --git a/metrics-collectd/src/main/java/com/codahale/metrics/collectd/Sender.java b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/Sender.java
new file mode 100644
index 0000000..fb63f12
--- /dev/null
+++ b/metrics-collectd/src/main/java/com/codahale/metrics/collectd/Sender.java
@@ -0,0 +1,50 @@
+package com.codahale.metrics.collectd;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+
+public class Sender {
+
+ private final String host;
+ private final int port;
+
+ private InetSocketAddress address;
+ private DatagramChannel channel;
+
+ public Sender(String host, int port) {
+ this.host = host;
+ this.port = port;
+ }
+
+ public void connect() throws IOException {
+ if (isConnected()) {
+ throw new IllegalStateException("Already connected");
+ }
+ if (host != null) {
+ address = new InetSocketAddress(host, port);
+ }
+ channel = DatagramChannel.open();
+ }
+
+ public boolean isConnected() {
+ return channel != null && !channel.socket().isClosed();
+ }
+
+ public void send(ByteBuffer buffer) throws IOException {
+ channel.send(buffer, address);
+ }
+
+ public void disconnect() throws IOException {
+ if (channel == null) {
+ return;
+ }
+ try {
+ channel.close();
+ } finally {
+ channel = null;
+ }
+ }
+
+}
diff --git a/metrics-collectd/src/test/java/com/codahale/metrics/collectd/CollectdReporterSecurityTest.java b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/CollectdReporterSecurityTest.java
new file mode 100644
index 0000000..5bedba4
--- /dev/null
+++ b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/CollectdReporterSecurityTest.java
@@ -0,0 +1,33 @@
+package com.codahale.metrics.collectd;
+
+import com.codahale.metrics.MetricRegistry;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+public class CollectdReporterSecurityTest {
+
+ private final MetricRegistry registry = new MetricRegistry();
+
+ @Test
+ public void testUnableSetSecurityLevelToSignWithoutUsername() {
+ assertThatIllegalArgumentException().isThrownBy(()->
+ CollectdReporter.forRegistry(registry)
+ .withHostName("eddie")
+ .withSecurityLevel(SecurityLevel.SIGN)
+ .withPassword("t1_g3r")
+ .build(new Sender("localhost", 25826)))
+ .withMessage("username is required for securityLevel: SIGN");
+ }
+
+ @Test
+ public void testUnableSetSecurityLevelToSignWithoutPassword() {
+ assertThatIllegalArgumentException().isThrownBy(()->
+ CollectdReporter.forRegistry(registry)
+ .withHostName("eddie")
+ .withSecurityLevel(SecurityLevel.SIGN)
+ .withUsername("scott")
+ .build(new Sender("localhost", 25826)))
+ .withMessage("password is required for securityLevel: SIGN");
+ }
+}
diff --git a/metrics-collectd/src/test/java/com/codahale/metrics/collectd/CollectdReporterTest.java b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/CollectdReporterTest.java
new file mode 100644
index 0000000..ae78e47
--- /dev/null
+++ b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/CollectdReporterTest.java
@@ -0,0 +1,301 @@
+package com.codahale.metrics.collectd;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricAttribute;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
+import org.collectd.api.ValueList;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CollectdReporterTest {
+
+ @ClassRule
+ public static Receiver receiver = new Receiver(25826);
+
+ private final MetricRegistry registry = new MetricRegistry();
+ private CollectdReporter reporter;
+
+ @Before
+ public void setUp() {
+ reporter = CollectdReporter.forRegistry(registry)
+ .withHostName("eddie")
+ .build(new Sender("localhost", 25826));
+ }
+
+ @Test
+ public void reportsByteGauges() throws Exception {
+ reportsGauges((byte) 128);
+ }
+
+ @Test
+ public void reportsShortGauges() throws Exception {
+ reportsGauges((short) 2048);
+ }
+
+ @Test
+ public void reportsIntegerGauges() throws Exception {
+ reportsGauges(42);
+ }
+
+ @Test
+ public void reportsLongGauges() throws Exception {
+ reportsGauges(Long.MAX_VALUE);
+ }
+
+ @Test
+ public void reportsFloatGauges() throws Exception {
+ reportsGauges(0.25);
+ }
+
+ @Test
+ public void reportsDoubleGauges() throws Exception {
+ reportsGauges(0.125d);
+ }
+
+ private <T extends Number> void reportsGauges(T value) throws Exception {
+ reporter.report(
+ map("gauge", () -> value),
+ map(),
+ map(),
+ map(),
+ map());
+
+ assertThat(nextValues(receiver)).containsExactly(value.doubleValue());
+ }
+
+ @Test
+ public void reportsBooleanGauges() throws Exception {
+ reporter.report(
+ map("gauge", () -> true),
+ map(),
+ map(),
+ map(),
+ map());
+
+ assertThat(nextValues(receiver)).containsExactly(1d);
+
+ reporter.report(
+ map("gauge", () -> false),
+ map(),
+ map(),
+ map(),
+ map());
+
+ assertThat(nextValues(receiver)).containsExactly(0d);
+ }
+
+ @Test
+ public void doesNotReportStringGauges() throws Exception {
+ reporter.report(
+ map("unsupported", () -> "value"),
+ map(),
+ map(),
+ map(),
+ map());
+
+ assertThat(receiver.next()).isNull();
+ }
+
+ @Test
+ public void reportsCounters() throws Exception {
+ Counter counter = mock(Counter.class);
+ when(counter.getCount()).thenReturn(42L);
+
+ reporter.report(
+ map(),
+ map("api.rest.requests.count", counter),
+ map(),
+ map(),
+ map());
+
+ assertThat(nextValues(receiver)).containsExactly(42d);
+ }
+
+ @Test
+ public void reportsMeters() throws Exception {
+ 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);
+
+ reporter.report(
+ map(),
+ map(),
+ map(),
+ map("api.rest.requests", meter),
+ map());
+
+ assertThat(nextValues(receiver)).containsExactly(1d);
+ assertThat(nextValues(receiver)).containsExactly(2d);
+ assertThat(nextValues(receiver)).containsExactly(3d);
+ assertThat(nextValues(receiver)).containsExactly(4d);
+ assertThat(nextValues(receiver)).containsExactly(5d);
+ }
+
+ @Test
+ public void reportsHistograms() throws Exception {
+ Histogram histogram = mock(Histogram.class);
+ Snapshot snapshot = mock(Snapshot.class);
+ when(histogram.getCount()).thenReturn(1L);
+ when(histogram.getSnapshot()).thenReturn(snapshot);
+ 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);
+
+ reporter.report(
+ map(),
+ map(),
+ map("histogram", histogram),
+ map(),
+ map());
+
+ for (int i = 1; i <= 11; i++) {
+ assertThat(nextValues(receiver)).containsExactly((double) i);
+ }
+ }
+
+ @Test
+ public void reportsTimers() throws Exception {
+ Timer timer = mock(Timer.class);
+ Snapshot snapshot = mock(Snapshot.class);
+ when(timer.getSnapshot()).thenReturn(snapshot);
+ when(timer.getCount()).thenReturn(1L);
+ when(timer.getSnapshot()).thenReturn(snapshot);
+ when(snapshot.getMax()).thenReturn(MILLISECONDS.toNanos(100));
+ when(snapshot.getMean()).thenReturn((double) MILLISECONDS.toNanos(200));
+ when(snapshot.getMin()).thenReturn(MILLISECONDS.toNanos(300));
+ when(snapshot.getStdDev()).thenReturn((double) MILLISECONDS.toNanos(400));
+ when(snapshot.getMedian()).thenReturn((double) MILLISECONDS.toNanos(500));
+ when(snapshot.get75thPercentile()).thenReturn((double) MILLISECONDS.toNanos(600));
+ when(snapshot.get95thPercentile()).thenReturn((double) MILLISECONDS.toNanos(700));
+ when(snapshot.get98thPercentile()).thenReturn((double) MILLISECONDS.toNanos(800));
+ when(snapshot.get99thPercentile()).thenReturn((double) MILLISECONDS.toNanos(900));
+ when(snapshot.get999thPercentile()).thenReturn((double) MILLISECONDS.toNanos(1000));
+ when(timer.getOneMinuteRate()).thenReturn(11.0);
+ when(timer.getFiveMinuteRate()).thenReturn(12.0);
+ when(timer.getFifteenMinuteRate()).thenReturn(13.0);
+ when(timer.getMeanRate()).thenReturn(14.0);
+
+ reporter.report(
+ map(),
+ map(),
+ map(),
+ map(),
+ map("timer", timer));
+
+ assertThat(nextValues(receiver)).containsExactly(1d);
+ assertThat(nextValues(receiver)).containsExactly(100d);
+ assertThat(nextValues(receiver)).containsExactly(200d);
+ assertThat(nextValues(receiver)).containsExactly(300d);
+ assertThat(nextValues(receiver)).containsExactly(400d);
+ assertThat(nextValues(receiver)).containsExactly(500d);
+ assertThat(nextValues(receiver)).containsExactly(600d);
+ assertThat(nextValues(receiver)).containsExactly(700d);
+ assertThat(nextValues(receiver)).containsExactly(800d);
+ assertThat(nextValues(receiver)).containsExactly(900d);
+ assertThat(nextValues(receiver)).containsExactly(1000d);
+ assertThat(nextValues(receiver)).containsExactly(11d);
+ assertThat(nextValues(receiver)).containsExactly(12d);
+ assertThat(nextValues(receiver)).containsExactly(13d);
+ assertThat(nextValues(receiver)).containsExactly(14d);
+ }
+
+ @Test
+ public void doesNotReportDisabledMetricAttributes() 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);
+
+ CollectdReporter reporter = CollectdReporter.forRegistry(registry)
+ .withHostName("eddie")
+ .disabledMetricAttributes(EnumSet.of(MetricAttribute.M5_RATE, MetricAttribute.M15_RATE))
+ .build(new Sender("localhost", 25826));
+
+ reporter.report(
+ map(),
+ map("counter", counter),
+ map(),
+ map("meter", meter),
+ map());
+
+ assertThat(nextValues(receiver)).containsExactly(11d);
+ assertThat(nextValues(receiver)).containsExactly(1d);
+ assertThat(nextValues(receiver)).containsExactly(2d);
+ assertThat(nextValues(receiver)).containsExactly(5d);
+ }
+
+ @Test
+ public void sanitizesMetricName() throws Exception {
+ Counter counter = registry.counter("dash-illegal.slash/illegal");
+ counter.inc();
+
+ reporter.report();
+
+ ValueList values = receiver.next();
+ assertThat(values.getPlugin()).isEqualTo("dash_illegal.slash_illegal");
+ }
+
+ @Test
+ public void sanitizesMetricNameWithCustomMaxLength() throws Exception {
+ CollectdReporter customReporter = CollectdReporter.forRegistry(registry)
+ .withHostName("eddie")
+ .withMaxLength(20)
+ .build(new Sender("localhost", 25826));
+
+ Counter counter = registry.counter("dash-illegal.slash/illegal");
+ counter.inc();
+
+ customReporter.report();
+
+ ValueList values = receiver.next();
+ assertThat(values.getPlugin()).isEqualTo("dash_illegal.slash_i");
+ }
+
+ private <T> SortedMap<String, T> map() {
+ return Collections.emptySortedMap();
+ }
+
+ private <T> SortedMap<String, T> map(String name, T metric) {
+ final Map<String, T> map = Collections.singletonMap(name, metric);
+ return new TreeMap<>(map);
+ }
+
+ private List<Number> nextValues(Receiver receiver) throws Exception {
+ final ValueList valueList = receiver.next();
+ return valueList == null ? Collections.emptyList() : valueList.getValues();
+ }
+}
+
+
diff --git a/metrics-collectd/src/test/java/com/codahale/metrics/collectd/PacketWriterTest.java b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/PacketWriterTest.java
new file mode 100644
index 0000000..ecb94ad
--- /dev/null
+++ b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/PacketWriterTest.java
@@ -0,0 +1,194 @@
+package com.codahale.metrics.collectd;
+
+import org.junit.Test;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.Offset.offset;
+
+public class PacketWriterTest {
+
+ private MetaData metaData = new MetaData.Builder("nw-1.alpine.example.com", 1520961345L, 100)
+ .type("gauge")
+ .typeInstance("value")
+ .get();
+ private String username = "scott";
+ private String password = "t1_g$r";
+
+ @Test
+ public void testSignRequest() throws Exception {
+ AtomicBoolean packetVerified = new AtomicBoolean();
+ Sender sender = new Sender("localhost", 4009) {
+ @Override
+ public void send(ByteBuffer buffer) throws IOException {
+ short type = buffer.getShort();
+ assertThat(type).isEqualTo((short) 512);
+ short length = buffer.getShort();
+ assertThat(length).isEqualTo((short) 41);
+ byte[] packetSignature = new byte[32];
+ buffer.get(packetSignature, 0, 32);
+
+ byte[] packetUsername = new byte[length - 36];
+ buffer.get(packetUsername, 0, packetUsername.length);
+ assertThat(new String(packetUsername, UTF_8)).isEqualTo(username);
+
+ byte[] packet = new byte[buffer.remaining()];
+ buffer.get(packet);
+
+ byte[] usernameAndPacket = new byte[username.length() + packet.length];
+ System.arraycopy(packetUsername, 0, usernameAndPacket, 0, packetUsername.length);
+ System.arraycopy(packet, 0, usernameAndPacket, packetUsername.length, packet.length);
+ assertThat(sign(usernameAndPacket, password)).isEqualTo(packetSignature);
+
+ verifyPacket(packet);
+ packetVerified.set(true);
+ }
+
+ private byte[] sign(byte[] input, String password) {
+ Mac mac;
+ try {
+ mac = Mac.getInstance("HmacSHA256");
+ mac.init(new SecretKeySpec(password.getBytes(UTF_8), "HmacSHA256"));
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ return mac.doFinal(input);
+ }
+
+ };
+ PacketWriter packetWriter = new PacketWriter(sender, username, password, SecurityLevel.SIGN);
+ packetWriter.write(metaData, 42);
+ assertThat(packetVerified).isTrue();
+ }
+
+ @Test
+ public void testEncryptRequest() throws Exception {
+ AtomicBoolean packetVerified = new AtomicBoolean();
+ Sender sender = new Sender("localhost", 4009) {
+ @Override
+ public void send(ByteBuffer buffer) throws IOException {
+ short type = buffer.getShort();
+ assertThat(type).isEqualTo((short) 0x0210);
+ short length = buffer.getShort();
+ assertThat(length).isEqualTo((short) 134);
+ short usernameLength = buffer.getShort();
+ assertThat(usernameLength).isEqualTo((short) 5);
+ byte[] packetUsername = new byte[usernameLength];
+ buffer.get(packetUsername, 0, packetUsername.length);
+ assertThat(new String(packetUsername, UTF_8)).isEqualTo(username);
+
+ byte[] iv = new byte[16];
+ buffer.get(iv, 0, iv.length);
+ byte[] encryptedPacket = new byte[buffer.remaining()];
+ buffer.get(encryptedPacket);
+
+ byte[] decryptedPacket = decrypt(iv, encryptedPacket);
+ byte[] hash = new byte[20];
+ System.arraycopy(decryptedPacket, 0, hash, 0, 20);
+ byte[] rawData = new byte[decryptedPacket.length - 20];
+ System.arraycopy(decryptedPacket, 20, rawData, 0, decryptedPacket.length - 20);
+ assertThat(sha1(rawData)).isEqualTo(hash);
+
+ verifyPacket(rawData);
+ packetVerified.set(true);
+ }
+
+ private byte[] decrypt(byte[] iv, byte[] input) {
+ try {
+ Cipher cipher = Cipher.getInstance("AES_256/OFB/NoPadding");
+ cipher.init(Cipher.DECRYPT_MODE,
+ new SecretKeySpec(sha256(password.getBytes(UTF_8)), "AES"),
+ new IvParameterSpec(iv));
+ return cipher.doFinal(input);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private byte[] sha256(byte[] input) {
+ try {
+ return MessageDigest.getInstance("SHA-256").digest(input);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private byte[] sha1(byte[] input) {
+ try {
+ return MessageDigest.getInstance("SHA-1").digest(input);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ PacketWriter packetWriter = new PacketWriter(sender, username, password, SecurityLevel.ENCRYPT);
+ packetWriter.write(metaData, 42);
+ assertThat(packetVerified).isTrue();
+ }
+
+ private void verifyPacket(byte[] packetArr) {
+ ByteBuffer packet = ByteBuffer.wrap(packetArr);
+
+ short hostType = packet.getShort();
+ assertThat(hostType).isEqualTo((short) 0);
+ short hostLength = packet.getShort();
+ assertThat(hostLength).isEqualTo((short) 28);
+ byte[] host = new byte[hostLength - 5];
+ packet.get(host, 0, host.length);
+ assertThat(new String(host, UTF_8)).isEqualTo("nw-1.alpine.example.com");
+ assertThat(packet.get()).isEqualTo((byte) 0);
+
+ short timestampType = packet.getShort();
+ assertThat(timestampType).isEqualTo((short) 1);
+ short timestampLength = packet.getShort();
+ assertThat(timestampLength).isEqualTo((short) 12);
+ assertThat(packet.getLong()).isEqualTo(1520961345L);
+
+ short typeType = packet.getShort();
+ assertThat(typeType).isEqualTo((short) 4);
+ short typeLength = packet.getShort();
+ assertThat(typeLength).isEqualTo((short) 10);
+ byte[] type = new byte[typeLength - 5];
+ packet.get(type, 0, type.length);
+ assertThat(new String(type, UTF_8)).isEqualTo("gauge");
+ assertThat(packet.get()).isEqualTo((byte) 0);
+
+ short typeInstanceType = packet.getShort();
+ assertThat(typeInstanceType).isEqualTo((short) 5);
+ short typeInstanceLength = packet.getShort();
+ assertThat(typeInstanceLength).isEqualTo((short) 10);
+ byte[] typeInstance = new byte[typeInstanceLength - 5];
+ packet.get(typeInstance, 0, typeInstance.length);
+ assertThat(new String(typeInstance, UTF_8)).isEqualTo("value");
+ assertThat(packet.get()).isEqualTo((byte) 0);
+
+ short periodType = packet.getShort();
+ assertThat(periodType).isEqualTo((short) 7);
+ short periodLength = packet.getShort();
+ assertThat(periodLength).isEqualTo((short) 12);
+ assertThat(packet.getLong()).isEqualTo(100);
+
+ short valuesType = packet.getShort();
+ assertThat(valuesType).isEqualTo((short) 6);
+ short valuesLength = packet.getShort();
+ assertThat(valuesLength).isEqualTo((short) 15);
+ short amountOfValues = packet.getShort();
+ assertThat(amountOfValues).isEqualTo((short) 1);
+ byte dataType = packet.get();
+ assertThat(dataType).isEqualTo((byte) 1);
+ assertThat(packet.order(ByteOrder.LITTLE_ENDIAN).getDouble()).isEqualTo(42.0, offset(0.01));
+ }
+
+}
\ No newline at end of file
diff --git a/metrics-collectd/src/test/java/com/codahale/metrics/collectd/Receiver.java b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/Receiver.java
new file mode 100644
index 0000000..396ec7f
--- /dev/null
+++ b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/Receiver.java
@@ -0,0 +1,63 @@
+package com.codahale.metrics.collectd;
+
+import org.collectd.api.Notification;
+import org.collectd.api.ValueList;
+import org.collectd.protocol.Dispatcher;
+import org.collectd.protocol.UdpReceiver;
+import org.junit.rules.ExternalResource;
+
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public final class Receiver extends ExternalResource {
+
+ private final int port;
+
+ private UdpReceiver receiver;
+ private DatagramSocket socket;
+ private BlockingQueue<ValueList> queue = new LinkedBlockingQueue<>();
+
+ public Receiver(int port) {
+ this.port = port;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ socket = new DatagramSocket(null);
+ socket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port));
+
+ receiver = new UdpReceiver(new Dispatcher() {
+ @Override
+ public void dispatch(ValueList values) {
+ queue.offer(new ValueList(values));
+ }
+
+ @Override
+ public void dispatch(Notification notification) {
+ throw new UnsupportedOperationException();
+ }
+ });
+ receiver.setPort(port);
+ new Thread(() -> {
+ try {
+ receiver.listen(socket);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }).start();
+ }
+
+ public ValueList next() throws InterruptedException {
+ return queue.poll(2, TimeUnit.SECONDS);
+ }
+
+ @Override
+ protected void after() {
+ receiver.shutdown();
+ socket.close();
+ }
+}
diff --git a/metrics-collectd/src/test/java/com/codahale/metrics/collectd/SanitizeTest.java b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/SanitizeTest.java
new file mode 100644
index 0000000..bf713ca
--- /dev/null
+++ b/metrics-collectd/src/test/java/com/codahale/metrics/collectd/SanitizeTest.java
@@ -0,0 +1,39 @@
+package com.codahale.metrics.collectd;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SanitizeTest {
+
+ private Sanitize sanitize = new Sanitize(Sanitize.DEFAULT_MAX_LENGTH);
+
+ @Test
+ public void replacesIllegalCharactersInName() throws Exception {
+ assertThat(sanitize.name("foo\u0000bar/baz-quux")).isEqualTo("foo_bar_baz_quux");
+ }
+
+ @Test
+ public void replacesIllegalCharactersInInstanceName() throws Exception {
+ assertThat(sanitize.instanceName("foo\u0000bar/baz-quux")).isEqualTo("foo_bar_baz-quux");
+ }
+
+ @Test
+ public void truncatesNamesExceedingMaxLength() throws Exception {
+ String longName = "01234567890123456789012345678901234567890123456789012345678901234567890123456789";
+ assertThat(sanitize.name(longName)).isEqualTo(longName.substring(0, (Sanitize.DEFAULT_MAX_LENGTH)));
+ }
+
+ @Test
+ public void truncatesNamesExceedingCustomMaxLength() throws Exception {
+ Sanitize customSanitize = new Sanitize(70);
+ String longName = "01234567890123456789012345678901234567890123456789012345678901234567890123456789";
+ assertThat(customSanitize.name(longName)).isEqualTo(longName.substring(0, 70));
+ }
+
+ @Test
+ public void replacesNonASCIICharacters() throws Exception {
+ assertThat(sanitize.name("M" + '\u00FC' + "nchen")).isEqualTo("M_nchen");
+ }
+
+}
diff --git a/metrics-core/pom.xml b/metrics-core/pom.xml
index 25635ad..db6d3a9 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-core</artifactId>
@@ -16,4 +16,55 @@
production. Metrics provides a powerful toolkit of ways to measure the behavior of critical
components in your production environment.
</description>
+
+ <properties>
+ <javaModuleName>com.codahale.metrics</javaModuleName>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>${commons-lang3.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
</project>
diff --git a/metrics-core/src/main/java/com/codahale/metrics/CachedGauge.java b/metrics-core/src/main/java/com/codahale/metrics/CachedGauge.java
index 565e34e..569a58e 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/CachedGauge.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/CachedGauge.java
@@ -2,24 +2,24 @@ package com.codahale.metrics;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
/**
* A {@link Gauge} implementation which caches its value for a period of time.
*
- * @param <T> the type of the gauge's value
+ * @param <T> the type of the gauge's value
*/
public abstract class CachedGauge<T> implements Gauge<T> {
private final Clock clock;
private final AtomicLong reloadAt;
private final long timeoutNS;
-
- private volatile T value;
+ private final AtomicReference<T> value;
/**
* Creates a new cached gauge with the given timeout period.
*
- * @param timeout the timeout
- * @param timeoutUnit the unit of {@code timeout}
+ * @param timeout the timeout
+ * @param timeoutUnit the unit of {@code timeout}
*/
protected CachedGauge(long timeout, TimeUnit timeoutUnit) {
this(Clock.defaultClock(), timeout, timeoutUnit);
@@ -28,14 +28,15 @@ public abstract class CachedGauge<T> implements Gauge<T> {
/**
* Creates a new cached gauge with the given clock and timeout period.
*
- * @param clock the clock used to calculate the timeout
- * @param timeout the timeout
- * @param timeoutUnit the unit of {@code timeout}
+ * @param clock the clock used to calculate the timeout
+ * @param timeout the timeout
+ * @param timeoutUnit the unit of {@code timeout}
*/
protected CachedGauge(Clock clock, long timeout, TimeUnit timeoutUnit) {
this.clock = clock;
- this.reloadAt = new AtomicLong(0);
+ this.reloadAt = new AtomicLong(clock.getTick());
this.timeoutNS = timeoutUnit.toNanos(timeout);
+ this.value = new AtomicReference<>();
}
/**
@@ -47,14 +48,19 @@ public abstract class CachedGauge<T> implements Gauge<T> {
@Override
public T getValue() {
- if (shouldLoad()) {
- this.value = loadValue();
+ T currentValue = this.value.get();
+ if (shouldLoad() || currentValue == null) {
+ T newValue = loadValue();
+ if (!this.value.compareAndSet(currentValue, newValue)) {
+ return this.value.get();
+ }
+ return newValue;
}
- return value;
+ return currentValue;
}
private boolean shouldLoad() {
- for (; ; ) {
+ for ( ;; ) {
final long time = clock.getTick();
final long current = reloadAt.get();
if (current > time) {
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java b/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java
index 8caf4e0..5acde4a 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java
@@ -1,13 +1,12 @@
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.Deque;
import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.ListIterator;
+
+import static java.lang.System.arraycopy;
+import static java.util.Arrays.binarySearch;
class ChunkedAssociativeLongArray {
private static final long[] EMPTY = new long[0];
@@ -15,25 +14,16 @@ class ChunkedAssociativeLongArray {
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>>();
+ private final ArrayDeque<SoftReference<Chunk>> chunksCache = new ArrayDeque<>();
- /*
- * 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>();
+ private final Deque<Chunk> chunks = new ArrayDeque<>();
ChunkedAssociativeLongArray() {
this(DEFAULT_CHUNK_SIZE);
@@ -61,44 +51,37 @@ class ChunkedAssociativeLongArray {
private void freeChunk(Chunk chunk) {
if (chunksCache.size() < MAX_CACHE_SIZE) {
- chunksCache.add(new SoftReference<Chunk>(chunk));
+ chunksCache.add(new SoftReference<>(chunk));
}
}
synchronized boolean put(long key, long value) {
Chunk activeChunk = chunks.peekLast();
-
- if (activeChunk == null) { // lazy chunk creation
+ if (activeChunk != null && activeChunk.cursor != 0 && activeChunk.keys[activeChunk.cursor - 1] > key) {
+ // key should be the same as last inserted or bigger
+ return false;
+ }
+ if (activeChunk == null || activeChunk.cursor - activeChunk.startIndex == activeChunk.chunkSize) {
+ // The last chunk doesn't exist or full
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();
+ final int valuesSize = size();
if (valuesSize == 0) {
return EMPTY;
}
- long[] values = new long[valuesSize];
+ final long[] values = new long[valuesSize];
int valuesIndex = 0;
- for (Chunk copySourceChunk : chunks) {
- int length = copySourceChunk.cursor - copySourceChunk.startIndex;
+ for (Chunk chunk : chunks) {
+ int length = chunk.cursor - chunk.startIndex;
int itemsToCopy = Math.min(valuesSize - valuesIndex, length);
- arraycopy(copySourceChunk.values, copySourceChunk.startIndex, values, valuesIndex, itemsToCopy);
+ arraycopy(chunk.values, chunk.startIndex, values, valuesIndex, itemsToCopy);
valuesIndex += length;
}
return values;
@@ -113,18 +96,17 @@ class ChunkedAssociativeLongArray {
}
synchronized String out() {
- Iterator<Chunk> fromTailIterator = chunks.iterator();
- StringBuilder builder = new StringBuilder();
- while (fromTailIterator.hasNext()) {
- Chunk copySourceChunk = fromTailIterator.next();
+ final StringBuilder builder = new StringBuilder();
+ final Iterator<Chunk> iterator = chunks.iterator();
+ while (iterator.hasNext()) {
+ final Chunk chunk = iterator.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(' ');
+ for (int i = chunk.startIndex; i < chunk.cursor; i++) {
+ builder.append('(').append(chunk.keys[i]).append(": ")
+ .append(chunk.values[i]).append(')').append(' ');
}
builder.append(']');
- if (fromTailIterator.hasNext()) {
+ if (iterator.hasNext()) {
builder.append("->");
}
}
@@ -133,10 +115,9 @@ class ChunkedAssociativeLongArray {
/**
* Try to trim all beyond specified boundaries.
- * All items that are less then startKey or greater/equals then endKey
*
- * @param startKey
- * @param endKey
+ * @param startKey the start value for which all elements less than it should be removed.
+ * @param endKey the end value for which all elements greater/equals than it should be removed.
*/
synchronized void trim(long startKey, long endKey) {
/*
@@ -144,83 +125,33 @@ class ChunkedAssociativeLongArray {
* |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)) {
+ final Iterator<Chunk> descendingIterator = chunks.descendingIterator();
+ while (descendingIterator.hasNext()) {
+ final Chunk currentTail = descendingIterator.next();
+ if (isFirstElementIsEmptyOrGreaterEqualThanKey(currentTail, endKey)) {
freeChunk(currentTail);
- fromTailIterator.remove();
+ descendingIterator.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;
- }
+ currentTail.cursor = findFirstIndexOfGreaterEqualElements(currentTail.keys, currentTail.startIndex,
+ currentTail.cursor, endKey);
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();
+ final Iterator<Chunk> iterator = chunks.iterator();
+ while (iterator.hasNext()) {
+ final Chunk currentHead = iterator.next();
+ if (isLastElementIsLessThanKey(currentHead, startKey)) {
+ freeChunk(currentHead);
+ iterator.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;
+ final int newStartIndex = findFirstIndexOfGreaterEqualElements(currentHead.keys, currentHead.startIndex,
+ currentHead.cursor, startKey);
+ if (currentHead.startIndex != newStartIndex) {
+ currentHead.startIndex = newStartIndex;
+ currentHead.chunkSize = currentHead.cursor - currentHead.startIndex;
}
+ break;
}
}
}
@@ -229,52 +160,20 @@ class ChunkedAssociativeLongArray {
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;
+ 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;
+ 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;
+ final int keyIndex = binarySearch(array, startIndex, endIndex, minKey);
+ return keyIndex < 0 ? -(keyIndex + 1) : keyIndex;
}
private static class Chunk {
@@ -292,15 +191,6 @@ class ChunkedAssociativeLongArray {
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;
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Clock.java b/metrics-core/src/main/java/com/codahale/metrics/Clock.java
index 2fb7b1e..5486812 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Clock.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Clock.java
@@ -1,8 +1,5 @@
package com.codahale.metrics;
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadMXBean;
-
/**
* An abstraction for how time passes. It is passed to {@link Timer} to track timing.
*/
@@ -23,17 +20,14 @@ public abstract class Clock {
return System.currentTimeMillis();
}
- private static final Clock DEFAULT = new UserTimeClock();
-
/**
* The default clock to use.
*
* @return the default {@link Clock} instance
- *
* @see Clock.UserTimeClock
*/
public static Clock defaultClock() {
- return DEFAULT;
+ return UserTimeClockHolder.DEFAULT;
}
/**
@@ -46,15 +40,7 @@ public abstract class Clock {
}
}
- /**
- * A clock implementation which returns the current thread's CPU time.
- */
- public static class CpuTimeClock extends Clock {
- private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean();
-
- @Override
- public long getTick() {
- return THREAD_MX_BEAN.getCurrentThreadCpuTime();
- }
+ private static class UserTimeClockHolder {
+ private static final Clock DEFAULT = new UserTimeClock();
}
}
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 e61095c..db559c8 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/ConsoleReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/ConsoleReporter.java
@@ -2,7 +2,13 @@ package com.codahale.metrics;
import java.io.PrintStream;
import java.text.DateFormat;
-import java.util.*;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TimeZone;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -174,16 +180,16 @@ public class ConsoleReporter extends ScheduledReporter {
*/
public ConsoleReporter build() {
return new ConsoleReporter(registry,
- output,
- locale,
- clock,
- timeZone,
- rateUnit,
- durationUnit,
- filter,
- executor,
- shutdownExecutorOnStop,
- disabledMetricAttributes);
+ output,
+ locale,
+ clock,
+ timeZone,
+ rateUnit,
+ durationUnit,
+ filter,
+ executor,
+ shutdownExecutorOnStop,
+ disabledMetricAttributes);
}
}
@@ -210,12 +216,13 @@ public class ConsoleReporter extends ScheduledReporter {
this.locale = locale;
this.clock = clock;
this.dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT,
- DateFormat.MEDIUM,
- locale);
+ DateFormat.MEDIUM,
+ locale);
dateFormat.setTimeZone(timeZone);
}
@Override
+ @SuppressWarnings("rawtypes")
public void report(SortedMap<String, Gauge> gauges,
SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms,
@@ -229,7 +236,7 @@ public class ConsoleReporter extends ScheduledReporter {
printWithBanner("-- Gauges", '-');
for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
output.println(entry.getKey());
- printGauge(entry);
+ printGauge(entry.getValue());
}
output.println();
}
@@ -286,8 +293,8 @@ public class ConsoleReporter extends ScheduledReporter {
output.printf(locale, " count = %d%n", entry.getValue().getCount());
}
- private void printGauge(Map.Entry<String, Gauge> entry) {
- output.printf(locale, " value = %s%n", entry.getValue().getValue());
+ private void printGauge(Gauge<?> gauge) {
+ output.printf(locale, " value = %s%n", gauge.getValue());
}
private void printHistogram(Histogram histogram) {
@@ -336,11 +343,12 @@ public class ConsoleReporter extends ScheduledReporter {
/**
* Print only if the attribute is enabled
- * @param type Metric attribute
+ *
+ * @param type Metric attribute
* @param status Status to be logged
*/
private void printIfEnabled(MetricAttribute type, String status) {
- if(getDisabledMetricAttributes().contains(type)) {
+ if (getDisabledMetricAttributes().contains(type)) {
return;
}
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 9aa6ba7..f956d03 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Counter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Counter.java
@@ -1,13 +1,15 @@
package com.codahale.metrics;
+import java.util.concurrent.atomic.LongAdder;
+
/**
* An incrementing and decrementing counter metric.
*/
public class Counter implements Metric, Counting {
- private final LongAdderAdapter count;
+ private final LongAdder count;
public Counter() {
- this.count = LongAdderProxy.create();
+ this.count = new LongAdder();
}
/**
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 e3e6bc1..2b9d860 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/CsvReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/CsvReporter.java
@@ -3,18 +3,25 @@ package com.codahale.metrics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.*;
-import java.nio.charset.Charset;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
/**
* A reporter which creates a comma-separated values file of the measurements for each metric.
*/
public class CsvReporter extends ScheduledReporter {
+ private static final String DEFAULT_SEPARATOR = ",";
+
/**
* Returns a new {@link Builder} for {@link CsvReporter}.
*
@@ -32,6 +39,7 @@ public class CsvReporter extends ScheduledReporter {
public static class Builder {
private final MetricRegistry registry;
private Locale locale;
+ private String separator;
private TimeUnit rateUnit;
private TimeUnit durationUnit;
private Clock clock;
@@ -43,6 +51,7 @@ public class CsvReporter extends ScheduledReporter {
private Builder(MetricRegistry registry) {
this.registry = registry;
this.locale = Locale.getDefault();
+ this.separator = DEFAULT_SEPARATOR;
this.rateUnit = TimeUnit.SECONDS;
this.durationUnit = TimeUnit.MILLISECONDS;
this.clock = Clock.defaultClock();
@@ -111,6 +120,17 @@ public class CsvReporter extends ScheduledReporter {
return this;
}
+ /**
+ * Use the given string to use as the separator for values.
+ *
+ * @param separator the string to use for the separator.
+ * @return {@code this}
+ */
+ public Builder withSeparator(String separator) {
+ this.separator = separator;
+ return this;
+ }
+
/**
* Use the given {@link Clock} instance for the time.
*
@@ -147,29 +167,39 @@ public class CsvReporter extends ScheduledReporter {
*/
public CsvReporter build(File directory) {
return new CsvReporter(registry,
- directory,
- locale,
- rateUnit,
- durationUnit,
- clock,
- filter,
- executor,
- shutdownExecutorOnStop,
- csvFileProvider);
+ directory,
+ locale,
+ separator,
+ rateUnit,
+ durationUnit,
+ clock,
+ filter,
+ executor,
+ shutdownExecutorOnStop,
+ csvFileProvider);
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(CsvReporter.class);
- private static final Charset UTF_8 = Charset.forName("UTF-8");
private final File directory;
private final Locale locale;
+ private final String separator;
private final Clock clock;
private final CsvFileProvider csvFileProvider;
+ private final String histogramFormat;
+ private final String meterFormat;
+ private final String timerFormat;
+
+ private final String timerHeader;
+ private final String meterHeader;
+ private final String histogramHeader;
+
private CsvReporter(MetricRegistry registry,
File directory,
Locale locale,
+ String separator,
TimeUnit rateUnit,
TimeUnit durationUnit,
Clock clock,
@@ -180,11 +210,21 @@ public class CsvReporter extends ScheduledReporter {
super(registry, "csv-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
this.directory = directory;
this.locale = locale;
+ this.separator = separator;
this.clock = clock;
this.csvFileProvider = csvFileProvider;
+
+ this.histogramFormat = String.join(separator, "%d", "%d", "%f", "%d", "%f", "%f", "%f", "%f", "%f", "%f", "%f");
+ this.meterFormat = String.join(separator, "%d", "%f", "%f", "%f", "%f", "events/%s");
+ this.timerFormat = String.join(separator, "%d", "%f", "%f", "%f", "%f", "%f", "%f", "%f", "%f", "%f", "%f", "%f", "%f", "%f", "%f", "calls/%s", "%s");
+
+ this.timerHeader = String.join(separator, "count", "max", "mean", "min", "stddev", "p50", "p75", "p95", "p98", "p99", "p999", "mean_rate", "m1_rate", "m5_rate", "m15_rate", "rate_unit", "duration_unit");
+ this.meterHeader = String.join(separator, "count", "mean_rate", "m1_rate", "m5_rate", "m15_rate", "rate_unit");
+ this.histogramHeader = String.join(separator, "count", "max", "mean", "min", "stddev", "p50", "p75", "p95", "p98", "p99", "p999");
}
@Override
+ @SuppressWarnings("rawtypes")
public void report(SortedMap<String, Gauge> gauges,
SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms,
@@ -217,66 +257,66 @@ public class CsvReporter extends ScheduledReporter {
final Snapshot snapshot = timer.getSnapshot();
report(timestamp,
- name,
- "count,max,mean,min,stddev,p50,p75,p95,p98,p99,p999,mean_rate,m1_rate,m5_rate,m15_rate,rate_unit,duration_unit",
- "%d,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,calls/%s,%s",
- timer.getCount(),
- convertDuration(snapshot.getMax()),
- convertDuration(snapshot.getMean()),
- convertDuration(snapshot.getMin()),
- convertDuration(snapshot.getStdDev()),
- convertDuration(snapshot.getMedian()),
- convertDuration(snapshot.get75thPercentile()),
- convertDuration(snapshot.get95thPercentile()),
- convertDuration(snapshot.get98thPercentile()),
- convertDuration(snapshot.get99thPercentile()),
- convertDuration(snapshot.get999thPercentile()),
- convertRate(timer.getMeanRate()),
- convertRate(timer.getOneMinuteRate()),
- convertRate(timer.getFiveMinuteRate()),
- convertRate(timer.getFifteenMinuteRate()),
- getRateUnit(),
- getDurationUnit());
+ name,
+ timerHeader,
+ timerFormat,
+ timer.getCount(),
+ convertDuration(snapshot.getMax()),
+ convertDuration(snapshot.getMean()),
+ convertDuration(snapshot.getMin()),
+ convertDuration(snapshot.getStdDev()),
+ convertDuration(snapshot.getMedian()),
+ convertDuration(snapshot.get75thPercentile()),
+ convertDuration(snapshot.get95thPercentile()),
+ convertDuration(snapshot.get98thPercentile()),
+ convertDuration(snapshot.get99thPercentile()),
+ convertDuration(snapshot.get999thPercentile()),
+ convertRate(timer.getMeanRate()),
+ convertRate(timer.getOneMinuteRate()),
+ convertRate(timer.getFiveMinuteRate()),
+ convertRate(timer.getFifteenMinuteRate()),
+ getRateUnit(),
+ getDurationUnit());
}
private void reportMeter(long timestamp, String name, Meter meter) {
report(timestamp,
- name,
- "count,mean_rate,m1_rate,m5_rate,m15_rate,rate_unit",
- "%d,%f,%f,%f,%f,events/%s",
- meter.getCount(),
- convertRate(meter.getMeanRate()),
- convertRate(meter.getOneMinuteRate()),
- convertRate(meter.getFiveMinuteRate()),
- convertRate(meter.getFifteenMinuteRate()),
- getRateUnit());
+ name,
+ meterHeader,
+ meterFormat,
+ meter.getCount(),
+ convertRate(meter.getMeanRate()),
+ convertRate(meter.getOneMinuteRate()),
+ convertRate(meter.getFiveMinuteRate()),
+ convertRate(meter.getFifteenMinuteRate()),
+ getRateUnit());
}
private void reportHistogram(long timestamp, String name, Histogram histogram) {
final Snapshot snapshot = histogram.getSnapshot();
report(timestamp,
- name,
- "count,max,mean,min,stddev,p50,p75,p95,p98,p99,p999",
- "%d,%d,%f,%d,%f,%f,%f,%f,%f,%f,%f",
- histogram.getCount(),
- snapshot.getMax(),
- snapshot.getMean(),
- snapshot.getMin(),
- snapshot.getStdDev(),
- snapshot.getMedian(),
- snapshot.get75thPercentile(),
- snapshot.get95thPercentile(),
- snapshot.get98thPercentile(),
- snapshot.get99thPercentile(),
- snapshot.get999thPercentile());
+ name,
+ histogramHeader,
+ histogramFormat,
+ histogram.getCount(),
+ snapshot.getMax(),
+ snapshot.getMean(),
+ snapshot.getMin(),
+ snapshot.getStdDev(),
+ snapshot.getMedian(),
+ snapshot.get75thPercentile(),
+ snapshot.get95thPercentile(),
+ snapshot.get98thPercentile(),
+ snapshot.get99thPercentile(),
+ snapshot.get999thPercentile());
}
private void reportCounter(long timestamp, String name, Counter counter) {
report(timestamp, name, "count", "%d", counter.getCount());
}
- private void reportGauge(long timestamp, String name, Gauge gauge) {
+ private void reportGauge(long timestamp, String name, Gauge<?> gauge) {
report(timestamp, name, "value", "%s", gauge.getValue());
}
@@ -285,14 +325,12 @@ public class CsvReporter extends ScheduledReporter {
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));
- try {
+ try (PrintWriter out = new PrintWriter(new OutputStreamWriter(
+ new FileOutputStream(file, true), UTF_8))) {
if (!fileAlreadyExists) {
- out.println("t," + header);
+ out.println("t" + separator + header);
}
- out.printf(locale, String.format(locale, "%d,%s%n", timestamp, line), values);
- } finally {
- out.close();
+ out.printf(locale, String.format(locale, "%d" + separator + "%s%n", timestamp, line), values);
}
}
} catch (IOException e) {
diff --git a/metrics-core/src/main/java/com/codahale/metrics/DefaultObjectNameFactory.java b/metrics-core/src/main/java/com/codahale/metrics/DefaultObjectNameFactory.java
deleted file mode 100644
index 17a028c..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/DefaultObjectNameFactory.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.codahale.metrics;
-
-import javax.management.MalformedObjectNameException;
-import javax.management.ObjectName;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class DefaultObjectNameFactory implements ObjectNameFactory {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(JmxReporter.class);
-
- @Override
- public ObjectName createName(String type, String domain, String name) {
- try {
- ObjectName objectName = new ObjectName(domain, "name", name);
- if (objectName.isPattern()) {
- objectName = new ObjectName(domain, "name", ObjectName.quote(name));
- }
- return objectName;
- } catch (MalformedObjectNameException e) {
- try {
- return new ObjectName(domain, "name", ObjectName.quote(name));
- } catch (MalformedObjectNameException e1) {
- LOGGER.warn("Unable to register {} {}", type, name, e1);
- throw new RuntimeException(e1);
- }
- }
- }
-
-}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/DefaultSettableGauge.java b/metrics-core/src/main/java/com/codahale/metrics/DefaultSettableGauge.java
new file mode 100644
index 0000000..d5ef193
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/DefaultSettableGauge.java
@@ -0,0 +1,43 @@
+package com.codahale.metrics;
+
+/**
+ * Similar to {@link Gauge}, but metric value is updated via calling {@link #setValue(T)} instead.
+ */
+public class DefaultSettableGauge<T> implements SettableGauge<T> {
+ private volatile T value;
+
+ /**
+ * Create an instance with no default value.
+ */
+ public DefaultSettableGauge() {
+ this(null);
+ }
+
+ /**
+ * Create an instance with a default value.
+ *
+ * @param defaultValue default value
+ */
+ public DefaultSettableGauge(T defaultValue) {
+ this.value = defaultValue;
+ }
+
+ /**
+ * Set the metric to a new value.
+ */
+ @Override
+ public void setValue(T value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the current value.
+ *
+ * @return the current value
+ */
+ @Override
+ public T getValue() {
+ return value;
+ }
+
+}
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 7738180..2d2e658 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/EWMA.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/EWMA.java
@@ -1,6 +1,7 @@
package com.codahale.metrics;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
import static java.lang.Math.exp;
@@ -8,9 +9,9 @@ import static java.lang.Math.exp;
* An exponentially-weighted moving average.
*
* @see <a href="http://www.teamquest.com/pdfs/whitepaper/ldavg1.pdf">UNIX Load Average Part 1: How
- * It Works</a>
+ * It Works</a>
* @see <a href="http://www.teamquest.com/pdfs/whitepaper/ldavg2.pdf">UNIX Load Average Part 2: Not
- * Your Average Average</a>
+ * Your Average Average</a>
* @see <a href="http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average">EMA</a>
*/
public class EWMA {
@@ -26,7 +27,7 @@ public class EWMA {
private volatile boolean initialized = false;
private volatile double rate = 0.0;
- private final LongAdderAdapter uncounted = LongAdderProxy.create();
+ private final LongAdder uncounted = new LongAdder();
private final double alpha, interval;
/**
@@ -87,7 +88,8 @@ public class EWMA {
final long count = uncounted.sumThenReset();
final double instantRate = count / interval;
if (initialized) {
- rate += (alpha * (instantRate - rate));
+ final double oldRate = this.rate;
+ rate = oldRate + (alpha * (instantRate - oldRate));
} else {
rate = instantRate;
initialized = true;
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ExponentialMovingAverages.java b/metrics-core/src/main/java/com/codahale/metrics/ExponentialMovingAverages.java
new file mode 100644
index 0000000..9879d28
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/ExponentialMovingAverages.java
@@ -0,0 +1,77 @@
+package com.codahale.metrics;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A triple (one, five and fifteen minutes) of exponentially-weighted moving average rates as needed by {@link Meter}.
+ * <p>
+ * The rates have the same exponential decay factor as the fifteen-minute load average in the
+ * {@code top} Unix command.
+ */
+public class ExponentialMovingAverages implements MovingAverages {
+
+ private static final long TICK_INTERVAL = TimeUnit.SECONDS.toNanos(5);
+
+ private final EWMA m1Rate = EWMA.oneMinuteEWMA();
+ private final EWMA m5Rate = EWMA.fiveMinuteEWMA();
+ private final EWMA m15Rate = EWMA.fifteenMinuteEWMA();
+
+ private final AtomicLong lastTick;
+ private final Clock clock;
+
+ /**
+ * Creates a new {@link ExponentialMovingAverages}.
+ */
+ public ExponentialMovingAverages() {
+ this(Clock.defaultClock());
+ }
+
+ /**
+ * Creates a new {@link ExponentialMovingAverages}.
+ */
+ public ExponentialMovingAverages(Clock clock) {
+ this.clock = clock;
+ this.lastTick = new AtomicLong(this.clock.getTick());
+ }
+
+ @Override
+ public void update(long n) {
+ m1Rate.update(n);
+ m5Rate.update(n);
+ m15Rate.update(n);
+ }
+
+ @Override
+ public void tickIfNecessary() {
+ final long oldTick = lastTick.get();
+ final long newTick = clock.getTick();
+ final long age = newTick - oldTick;
+ if (age > TICK_INTERVAL) {
+ final long newIntervalStartTick = newTick - age % TICK_INTERVAL;
+ if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) {
+ final long requiredTicks = age / TICK_INTERVAL;
+ for (long i = 0; i < requiredTicks; i++) {
+ m1Rate.tick();
+ m5Rate.tick();
+ m15Rate.tick();
+ }
+ }
+ }
+ }
+
+ @Override
+ public double getM1Rate() {
+ return m1Rate.getRate(TimeUnit.SECONDS);
+ }
+
+ @Override
+ public double getM5Rate() {
+ return m5Rate.getRate(TimeUnit.SECONDS);
+ }
+
+ @Override
+ public double getM15Rate() {
+ return m15Rate.getRate(TimeUnit.SECONDS);
+ }
+}
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 e8ca81e..21cb4c8 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/ExponentiallyDecayingReservoir.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/ExponentiallyDecayingReservoir.java
@@ -2,6 +2,7 @@ package com.codahale.metrics;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -18,7 +19,7 @@ import com.codahale.metrics.WeightedSnapshot.WeightedSample;
*
* @see <a href="http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf">
* Cormode et al. Forward Decay: A Practical Time Decay Model for Streaming Systems. ICDE '09:
- * Proceedings of the 2009 IEEE International Conference on Data Engineering (2009)</a>
+ * Proceedings of the 2009 IEEE International Conference on Data Engineering (2009)</a>
*/
public class ExponentiallyDecayingReservoir implements Reservoir {
private static final int DEFAULT_SIZE = 1028;
@@ -31,7 +32,7 @@ public class ExponentiallyDecayingReservoir implements Reservoir {
private final int size;
private final AtomicLong count;
private volatile long startTime;
- private final AtomicLong nextScaleTime;
+ private final AtomicLong lastScaleTick;
private final Clock clock;
/**
@@ -63,14 +64,14 @@ public class ExponentiallyDecayingReservoir implements Reservoir {
* @param clock the clock used to timestamp samples and track rescaling
*/
public ExponentiallyDecayingReservoir(int size, double alpha, Clock clock) {
- this.values = new ConcurrentSkipListMap<Double, WeightedSample>();
+ this.values = new ConcurrentSkipListMap<>();
this.lock = new ReentrantReadWriteLock();
this.alpha = alpha;
this.size = size;
this.clock = clock;
this.count = new AtomicLong(0);
this.startTime = currentTimeInSeconds();
- this.nextScaleTime = new AtomicLong(clock.getTick() + RESCALE_THRESHOLD);
+ this.lastScaleTick = new AtomicLong(clock.getTick());
}
@Override
@@ -95,10 +96,10 @@ public class ExponentiallyDecayingReservoir implements Reservoir {
try {
final double itemWeight = weight(timestamp - startTime);
final WeightedSample sample = new WeightedSample(value, itemWeight);
- final double priority = itemWeight / ThreadLocalRandomProxy.current().nextDouble();
-
+ final double priority = itemWeight / ThreadLocalRandom.current().nextDouble();
+
final long newCount = count.incrementAndGet();
- if (newCount <= size) {
+ if (newCount <= size || values.isEmpty()) {
values.put(priority, sample);
} else {
Double first = values.firstKey();
@@ -116,9 +117,9 @@ public class ExponentiallyDecayingReservoir implements Reservoir {
private void rescaleIfNeeded() {
final long now = clock.getTick();
- final long next = nextScaleTime.get();
- if (now >= next) {
- rescale(now, next);
+ final long lastScaleTickSnapshot = lastScaleTick.get();
+ if (now - lastScaleTickSnapshot >= RESCALE_THRESHOLD) {
+ rescale(now, lastScaleTickSnapshot);
}
}
@@ -159,17 +160,17 @@ public class ExponentiallyDecayingReservoir implements Reservoir {
* landmark Lβ² (and then use this new Lβ² at query time). This can be done with
* a linear pass over whatever data structure is being used."
*/
- private void rescale(long now, long next) {
+ private void rescale(long now, long lastTick) {
lockForRescale();
try {
- if (nextScaleTime.compareAndSet(next, now + RESCALE_THRESHOLD)) {
+ if (lastScaleTick.compareAndSet(lastTick, now)) {
final long oldStartTime = startTime;
this.startTime = currentTimeInSeconds();
final double scalingFactor = exp(-alpha * (startTime - oldStartTime));
if (Double.compare(scalingFactor, 0) == 0) {
values.clear();
} else {
- final ArrayList<Double> keys = new ArrayList<Double>(values.keySet());
+ final ArrayList<Double> keys = new ArrayList<>(values.keySet());
for (Double key : keys) {
final WeightedSample sample = values.remove(key);
final WeightedSample newSample = new WeightedSample(sample.value, sample.weight * scalingFactor);
diff --git a/metrics-core/src/main/java/com/codahale/metrics/FixedNameCsvFileProvider.java b/metrics-core/src/main/java/com/codahale/metrics/FixedNameCsvFileProvider.java
index eaa8538..db91c17 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/FixedNameCsvFileProvider.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/FixedNameCsvFileProvider.java
@@ -16,7 +16,6 @@ public class FixedNameCsvFileProvider implements CsvFileProvider {
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;
+ return metricName.replaceFirst("^/", "").replaceAll("/", ".");
}
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Gauge.java b/metrics-core/src/main/java/com/codahale/metrics/Gauge.java
index 09a64e9..eade65b 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Gauge.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Gauge.java
@@ -15,6 +15,7 @@ package com.codahale.metrics;
*
* @param <T> the type of the metric's value
*/
+@FunctionalInterface
public interface Gauge<T> extends Metric {
/**
* Returns the metric's current value.
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 48f877b..e499a07 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Histogram.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Histogram.java
@@ -1,14 +1,16 @@
package com.codahale.metrics;
+import java.util.concurrent.atomic.LongAdder;
+
/**
* A metric which calculates the distribution of a value.
*
* @see <a href="http://www.johndcook.com/standard_deviation.html">Accurately computing running
- * variance</a>
+ * variance</a>
*/
public class Histogram implements Metric, Sampling, Counting {
private final Reservoir reservoir;
- private final LongAdderAdapter count;
+ private final LongAdder count;
/**
* Creates a new {@link Histogram} with the given reservoir.
@@ -17,7 +19,7 @@ public class Histogram implements Metric, Sampling, Counting {
*/
public Histogram(Reservoir reservoir) {
this.reservoir = reservoir;
- this.count = LongAdderProxy.create();
+ this.count = new LongAdder();
}
/**
diff --git a/metrics-core/src/main/java/com/codahale/metrics/InstrumentedExecutorService.java b/metrics-core/src/main/java/com/codahale/metrics/InstrumentedExecutorService.java
index 1aa1d01..9603515 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/InstrumentedExecutorService.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/InstrumentedExecutorService.java
@@ -3,28 +3,32 @@ package com.codahale.metrics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
-import java.util.concurrent.Callable;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
/**
* An {@link ExecutorService} that monitors the number of tasks submitted, running,
* completed and also keeps a {@link Timer} for the task duration.
- * <p/>
+ * <p>
* It will register the metrics using the given (or auto-generated) name as classifier, e.g:
* "your-executor-service.submitted", "your-executor-service.running", etc.
*/
public class InstrumentedExecutorService implements ExecutorService {
- private static final AtomicLong nameCounter = new AtomicLong();
+ private static final AtomicLong NAME_COUNTER = new AtomicLong();
private final ExecutorService delegate;
private final Meter submitted;
private final Counter running;
private final Meter completed;
+ private final Timer idle;
private final Timer duration;
/**
@@ -34,7 +38,7 @@ public class InstrumentedExecutorService implements ExecutorService {
* @param registry {@link MetricRegistry} that will contain the metrics.
*/
public InstrumentedExecutorService(ExecutorService delegate, MetricRegistry registry) {
- this(delegate, registry, "instrumented-delegate-" + nameCounter.incrementAndGet());
+ this(delegate, registry, "instrumented-delegate-" + NAME_COUNTER.incrementAndGet());
}
/**
@@ -49,7 +53,37 @@ public class InstrumentedExecutorService implements ExecutorService {
this.submitted = registry.meter(MetricRegistry.name(name, "submitted"));
this.running = registry.counter(MetricRegistry.name(name, "running"));
this.completed = registry.meter(MetricRegistry.name(name, "completed"));
+ this.idle = registry.timer(MetricRegistry.name(name, "idle"));
this.duration = registry.timer(MetricRegistry.name(name, "duration"));
+
+ if (delegate instanceof ThreadPoolExecutor) {
+ ThreadPoolExecutor executor = (ThreadPoolExecutor) delegate;
+ registry.registerGauge(MetricRegistry.name(name, "pool.size"),
+ executor::getPoolSize);
+ registry.registerGauge(MetricRegistry.name(name, "pool.core"),
+ executor::getCorePoolSize);
+ registry.registerGauge(MetricRegistry.name(name, "pool.max"),
+ executor::getMaximumPoolSize);
+ final BlockingQueue<Runnable> queue = executor.getQueue();
+ registry.registerGauge(MetricRegistry.name(name, "tasks.active"),
+ executor::getActiveCount);
+ registry.registerGauge(MetricRegistry.name(name, "tasks.completed"),
+ executor::getCompletedTaskCount);
+ registry.registerGauge(MetricRegistry.name(name, "tasks.queued"),
+ queue::size);
+ registry.registerGauge(MetricRegistry.name(name, "tasks.capacity"),
+ queue::remainingCapacity);
+ } else if (delegate instanceof ForkJoinPool) {
+ ForkJoinPool forkJoinPool = (ForkJoinPool) delegate;
+ registry.registerGauge(MetricRegistry.name(name, "tasks.stolen"),
+ forkJoinPool::getStealCount);
+ registry.registerGauge(MetricRegistry.name(name, "tasks.queued"),
+ forkJoinPool::getQueuedTaskCount);
+ registry.registerGauge(MetricRegistry.name(name, "threads.active"),
+ forkJoinPool::getActiveThreadCount);
+ registry.registerGauge(MetricRegistry.name(name, "threads.running"),
+ forkJoinPool::getRunningThreadCount);
+ }
}
/**
@@ -85,7 +119,7 @@ public class InstrumentedExecutorService implements ExecutorService {
@Override
public <T> Future<T> submit(Callable<T> task) {
submitted.mark();
- return delegate.submit(new InstrumentedCallable<T>(task));
+ return delegate.submit(new InstrumentedCallable<>(task));
}
/**
@@ -129,9 +163,9 @@ public class InstrumentedExecutorService implements ExecutorService {
}
private <T> Collection<? extends Callable<T>> instrument(Collection<? extends Callable<T>> tasks) {
- final List<InstrumentedCallable<T>> instrumented = new ArrayList<InstrumentedCallable<T>>(tasks.size());
+ final List<InstrumentedCallable<T>> instrumented = new ArrayList<>(tasks.size());
for (Callable<T> task : tasks) {
- instrumented.add(new InstrumentedCallable<T>(task));
+ instrumented.add(new InstrumentedCallable<>(task));
}
return instrumented;
}
@@ -163,19 +197,20 @@ public class InstrumentedExecutorService implements ExecutorService {
private class InstrumentedRunnable implements Runnable {
private final Runnable task;
+ private final Timer.Context idleContext;
InstrumentedRunnable(Runnable task) {
this.task = task;
+ this.idleContext = idle.time();
}
@Override
public void run() {
+ idleContext.stop();
running.inc();
- final Timer.Context context = duration.time();
- try {
+ try (Timer.Context durationContext = duration.time()) {
task.run();
} finally {
- context.stop();
running.dec();
completed.mark();
}
@@ -184,19 +219,20 @@ public class InstrumentedExecutorService implements ExecutorService {
private class InstrumentedCallable<T> implements Callable<T> {
private final Callable<T> callable;
+ private final Timer.Context idleContext;
InstrumentedCallable(Callable<T> callable) {
this.callable = callable;
+ this.idleContext = idle.time();
}
@Override
public T call() throws Exception {
+ idleContext.stop();
running.inc();
- final Timer.Context context = duration.time();
- try {
+ try (Timer.Context context = duration.time()) {
return callable.call();
} finally {
- context.stop();
running.dec();
completed.mark();
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/InstrumentedScheduledExecutorService.java b/metrics-core/src/main/java/com/codahale/metrics/InstrumentedScheduledExecutorService.java
index b6a67d3..2491571 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/InstrumentedScheduledExecutorService.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/InstrumentedScheduledExecutorService.java
@@ -3,18 +3,24 @@ package com.codahale.metrics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.concurrent.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
/**
* An {@link ScheduledExecutorService} that monitors the number of tasks submitted, running,
* completed and also keeps a {@link Timer} for the task duration.
- * <p/>
+ * <p>
* It will register the metrics using the given (or auto-generated) name as classifier, e.g:
* "your-executor-service.submitted", "your-executor-service.running", etc.
*/
public class InstrumentedScheduledExecutorService implements ScheduledExecutorService {
- private static final AtomicLong nameCounter = new AtomicLong();
+ private static final AtomicLong NAME_COUNTER = new AtomicLong();
private final ScheduledExecutorService delegate;
@@ -35,7 +41,7 @@ public class InstrumentedScheduledExecutorService implements ScheduledExecutorSe
* @param registry {@link MetricRegistry} that will contain the metrics.
*/
public InstrumentedScheduledExecutorService(ScheduledExecutorService delegate, MetricRegistry registry) {
- this(delegate, registry, "instrumented-scheduled-executor-service-" + nameCounter.incrementAndGet());
+ this(delegate, registry, "instrumented-scheduled-executor-service-" + NAME_COUNTER.incrementAndGet());
}
/**
@@ -75,7 +81,7 @@ public class InstrumentedScheduledExecutorService implements ScheduledExecutorSe
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
scheduledOnce.mark();
- return delegate.schedule(new InstrumentedCallable<V>(callable), delay, unit);
+ return delegate.schedule(new InstrumentedCallable<>(callable), delay, unit);
}
/**
@@ -142,7 +148,7 @@ public class InstrumentedScheduledExecutorService implements ScheduledExecutorSe
@Override
public <T> Future<T> submit(Callable<T> task) {
submitted.mark();
- return delegate.submit(new InstrumentedCallable<T>(task));
+ return delegate.submit(new InstrumentedCallable<>(task));
}
/**
@@ -204,9 +210,9 @@ public class InstrumentedScheduledExecutorService implements ScheduledExecutorSe
}
private <T> Collection<? extends Callable<T>> instrument(Collection<? extends Callable<T>> tasks) {
- final List<InstrumentedCallable<T>> instrumented = new ArrayList<InstrumentedCallable<T>>(tasks.size());
+ final List<InstrumentedCallable<T>> instrumented = new ArrayList<>(tasks.size());
for (Callable<T> task : tasks) {
- instrumented.add(new InstrumentedCallable(task));
+ instrumented.add(new InstrumentedCallable<>(task));
}
return instrumented;
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/InstrumentedThreadFactory.java b/metrics-core/src/main/java/com/codahale/metrics/InstrumentedThreadFactory.java
index 2f29fed..b64c05f 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/InstrumentedThreadFactory.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/InstrumentedThreadFactory.java
@@ -5,12 +5,12 @@ import java.util.concurrent.atomic.AtomicLong;
/**
* A {@link ThreadFactory} that monitors the number of threads created, running and terminated.
- * <p/>
+ * <p>
* It will register the metrics using the given (or auto-generated) name as classifier, e.g:
* "your-thread-delegate.created", "your-thread-delegate.running", etc.
*/
public class InstrumentedThreadFactory implements ThreadFactory {
- private static final AtomicLong nameCounter = new AtomicLong();
+ private static final AtomicLong NAME_COUNTER = new AtomicLong();
private final ThreadFactory delegate;
private final Meter created;
@@ -24,7 +24,7 @@ public class InstrumentedThreadFactory implements ThreadFactory {
* @param registry {@link MetricRegistry} that will contain the metrics.
*/
public InstrumentedThreadFactory(ThreadFactory delegate, MetricRegistry registry) {
- this(delegate, registry, "instrumented-thread-delegate-" + nameCounter.incrementAndGet());
+ this(delegate, registry, "instrumented-thread-delegate-" + NAME_COUNTER.incrementAndGet());
}
/**
diff --git a/metrics-core/src/main/java/com/codahale/metrics/JvmAttributeGaugeSet.java b/metrics-core/src/main/java/com/codahale/metrics/JvmAttributeGaugeSet.java
deleted file mode 100644
index 77502c6..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/JvmAttributeGaugeSet.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.codahale.metrics;
-
-import java.lang.management.ManagementFactory;
-import java.lang.management.RuntimeMXBean;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * A set of gauges for the JVM name, vendor, and uptime.
- */
-public class JvmAttributeGaugeSet implements MetricSet {
- private final RuntimeMXBean runtime;
-
- /**
- * Creates a new set of gauges.
- */
- public JvmAttributeGaugeSet() {
- this(ManagementFactory.getRuntimeMXBean());
- }
-
- /**
- * Creates a new set of gauges with the given {@link RuntimeMXBean}.
- * @param runtime JVM management interface with access to system properties
- */
- public JvmAttributeGaugeSet(RuntimeMXBean runtime) {
- this.runtime = runtime;
- }
-
- @Override
- public Map<String, Metric> getMetrics() {
- final Map<String, Metric> gauges = new HashMap<String, Metric>();
-
- gauges.put("name", new Gauge<String>() {
- @Override
- public String getValue() {
- return runtime.getName();
- }
- });
-
- gauges.put("vendor", new Gauge<String>() {
- @Override
- public String getValue() {
- return String.format(Locale.US,
- "%s %s %s (%s)",
- runtime.getVmVendor(),
- runtime.getVmName(),
- runtime.getVmVersion(),
- runtime.getSpecVersion());
- }
- });
-
- gauges.put("uptime", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return runtime.getUptime();
- }
- });
-
- return Collections.unmodifiableMap(gauges);
- }
-}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/LockFreeExponentiallyDecayingReservoir.java b/metrics-core/src/main/java/com/codahale/metrics/LockFreeExponentiallyDecayingReservoir.java
new file mode 100644
index 0000000..cf258f0
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/LockFreeExponentiallyDecayingReservoir.java
@@ -0,0 +1,270 @@
+package com.codahale.metrics;
+
+import com.codahale.metrics.WeightedSnapshot.WeightedSample;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import java.util.function.BiConsumer;
+
+/**
+ * A lock-free exponentially-decaying random reservoir of {@code long}s. Uses Cormode et al's
+ * forward-decaying priority reservoir sampling method to produce a statistically representative
+ * sampling reservoir, exponentially biased towards newer entries.
+ *
+ * @see <a href="http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf">
+ * Cormode et al. Forward Decay: A Practical Time Decay Model for Streaming Systems. ICDE '09:
+ * Proceedings of the 2009 IEEE International Conference on Data Engineering (2009)</a>
+ *
+ * {@link LockFreeExponentiallyDecayingReservoir} is based closely on the {@link ExponentiallyDecayingReservoir},
+ * however it provides looser guarantees while completely avoiding locks.
+ *
+ * Looser guarantees:
+ * <ul>
+ * <li> Updates which occur concurrently with rescaling may be discarded if the orphaned state node is updated after
+ * rescale has replaced it. This condition has a greater probability as the rescale interval is reduced due to the
+ * increased frequency of rescaling. {@link #rescaleThresholdNanos} values below 30 seconds are not recommended.
+ * <li> Given a small rescale threshold, updates may attempt to rescale into a new bucket, but lose the CAS race
+ * and update into a newer bucket than expected. In these cases the measurement weight is reduced accordingly.
+ * <li>In the worst case, all concurrent threads updating the reservoir may attempt to rescale rather than
+ * a single thread holding an exclusive write lock. It's expected that the configuration is set such that
+ * rescaling is substantially less common than updating at peak load. Even so, when size is reasonably small
+ * it can be more efficient to rescale than to park and context switch.
+ * </ul>
+ *
+ * @author <a href="mailto:ckozak@ckozak.net">Carter Kozak</a>
+ */
+public final class LockFreeExponentiallyDecayingReservoir implements Reservoir {
+
+ private static final double SECONDS_PER_NANO = .000_000_001D;
+ private static final AtomicReferenceFieldUpdater<LockFreeExponentiallyDecayingReservoir, State> stateUpdater =
+ AtomicReferenceFieldUpdater.newUpdater(LockFreeExponentiallyDecayingReservoir.class, State.class, "state");
+
+ private final int size;
+ private final long rescaleThresholdNanos;
+ private final Clock clock;
+
+ private volatile State state;
+
+ private static final class State {
+
+ private static final AtomicIntegerFieldUpdater<State> countUpdater =
+ AtomicIntegerFieldUpdater.newUpdater(State.class, "count");
+
+ private final double alphaNanos;
+ private final int size;
+ private final long startTick;
+ // Count is updated after samples are successfully added to the map.
+ private final ConcurrentSkipListMap<Double, WeightedSample> values;
+
+ private volatile int count;
+
+ State(
+ double alphaNanos,
+ int size,
+ long startTick,
+ int count,
+ ConcurrentSkipListMap<Double, WeightedSample> values) {
+ this.alphaNanos = alphaNanos;
+ this.size = size;
+ this.startTick = startTick;
+ this.values = values;
+ this.count = count;
+ }
+
+ private void update(long value, long timestampNanos) {
+ double itemWeight = weight(timestampNanos - startTick);
+ double priority = itemWeight / ThreadLocalRandom.current().nextDouble();
+ boolean mapIsFull = count >= size;
+ if (!mapIsFull || values.firstKey() < priority) {
+ addSample(priority, value, itemWeight, mapIsFull);
+ }
+ }
+
+ private void addSample(double priority, long value, double itemWeight, boolean bypassIncrement) {
+ if (values.putIfAbsent(priority, new WeightedSample(value, itemWeight)) == null
+ && (bypassIncrement || countUpdater.incrementAndGet(this) > size)) {
+ values.pollFirstEntry();
+ }
+ }
+
+ /* "A common feature of the above techniquesβindeed, the key technique that
+ * allows us to track the decayed weights efficientlyβis that they maintain
+ * counts and other quantities based on g(ti β L), and only scale by g(t β L)
+ * at query time. But while g(ti βL)/g(tβL) is guaranteed to lie between zero
+ * and one, the intermediate values of g(ti β L) could become very large. For
+ * polynomial functions, these values should not grow too large, and should be
+ * effectively represented in practice by floating point values without loss of
+ * precision. For exponential functions, these values could grow quite large as
+ * new values of (ti β L) become large, and potentially exceed the capacity of
+ * common floating point types. However, since the values stored by the
+ * algorithms are linear combinations of g values (scaled sums), they can be
+ * rescaled relative to a new landmark. That is, by the analysis of exponential
+ * decay in Section III-A, the choice of L does not affect the final result. We
+ * can therefore multiply each value based on L by a factor of exp(βΞ±(Lβ² β L)),
+ * and obtain the correct value as if we had instead computed relative to a new
+ * landmark Lβ² (and then use this new Lβ² at query time). This can be done with
+ * a linear pass over whatever data structure is being used."
+ */
+ State rescale(long newTick) {
+ long durationNanos = newTick - startTick;
+ double scalingFactor = Math.exp(-alphaNanos * durationNanos);
+ int newCount = 0;
+ ConcurrentSkipListMap<Double, WeightedSample> newValues = new ConcurrentSkipListMap<>();
+ if (Double.compare(scalingFactor, 0) != 0) {
+ RescalingConsumer consumer = new RescalingConsumer(scalingFactor, newValues);
+ values.forEach(consumer);
+ // make sure the counter is in sync with the number of stored samples.
+ newCount = consumer.count;
+ }
+ // It's possible that more values were added while the map was scanned, those with the
+ // minimum priorities are removed.
+ while (newCount > size) {
+ Objects.requireNonNull(newValues.pollFirstEntry(), "Expected an entry");
+ newCount--;
+ }
+ return new State(alphaNanos, size, newTick, newCount, newValues);
+ }
+
+ private double weight(long durationNanos) {
+ return Math.exp(alphaNanos * durationNanos);
+ }
+ }
+
+ private static final class RescalingConsumer implements BiConsumer<Double, WeightedSample> {
+ private final double scalingFactor;
+ private final ConcurrentSkipListMap<Double, WeightedSample> values;
+ private int count;
+
+ RescalingConsumer(double scalingFactor, ConcurrentSkipListMap<Double, WeightedSample> values) {
+ this.scalingFactor = scalingFactor;
+ this.values = values;
+ }
+
+ @Override
+ public void accept(Double priority, WeightedSample sample) {
+ double newWeight = sample.weight * scalingFactor;
+ if (Double.compare(newWeight, 0) == 0) {
+ return;
+ }
+ WeightedSample newSample = new WeightedSample(sample.value, newWeight);
+ if (values.put(priority * scalingFactor, newSample) == null) {
+ count++;
+ }
+ }
+ }
+
+ private LockFreeExponentiallyDecayingReservoir(int size, double alpha, Duration rescaleThreshold, Clock clock) {
+ // Scale alpha to nanoseconds
+ double alphaNanos = alpha * SECONDS_PER_NANO;
+ this.size = size;
+ this.clock = clock;
+ this.rescaleThresholdNanos = rescaleThreshold.toNanos();
+ this.state = new State(alphaNanos, size, clock.getTick(), 0, new ConcurrentSkipListMap<>());
+ }
+
+ @Override
+ public int size() {
+ return Math.min(size, state.count);
+ }
+
+ @Override
+ public void update(long value) {
+ long now = clock.getTick();
+ rescaleIfNeeded(now).update(value, now);
+ }
+
+ private State rescaleIfNeeded(long currentTick) {
+ // This method is optimized for size so the check may be quickly inlined.
+ // Rescaling occurs substantially less frequently than the check itself.
+ State stateSnapshot = this.state;
+ if (currentTick - stateSnapshot.startTick >= rescaleThresholdNanos) {
+ return doRescale(currentTick, stateSnapshot);
+ }
+ return stateSnapshot;
+ }
+
+ private State doRescale(long currentTick, State stateSnapshot) {
+ State newState = stateSnapshot.rescale(currentTick);
+ if (stateUpdater.compareAndSet(this, stateSnapshot, newState)) {
+ // newState successfully installed
+ return newState;
+ }
+ // Otherwise another thread has won the race and we can return the result of a volatile read.
+ // It's possible this has taken so long that another update is required, however that's unlikely
+ // and no worse than the standard race between a rescale and update.
+ return this.state;
+ }
+
+ @Override
+ public Snapshot getSnapshot() {
+ State stateSnapshot = rescaleIfNeeded(clock.getTick());
+ return new WeightedSnapshot(stateSnapshot.values.values());
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * By default this uses a size of 1028 elements, which offers a 99.9%
+ * confidence level with a 5% margin of error assuming a normal distribution, and an alpha
+ * factor of 0.015, which heavily biases the reservoir to the past 5 minutes of measurements.
+ */
+ public static final class Builder {
+ private static final int DEFAULT_SIZE = 1028;
+ private static final double DEFAULT_ALPHA = 0.015D;
+ private static final Duration DEFAULT_RESCALE_THRESHOLD = Duration.ofHours(1);
+
+ private int size = DEFAULT_SIZE;
+ private double alpha = DEFAULT_ALPHA;
+ private Duration rescaleThreshold = DEFAULT_RESCALE_THRESHOLD;
+ private Clock clock = Clock.defaultClock();
+
+ private Builder() {}
+
+ /**
+ * Maximum number of samples to keep in the reservoir. Once this number is reached older samples are
+ * replaced (based on weight, with some amount of random jitter).
+ */
+ public Builder size(int value) {
+ if (value <= 0) {
+ throw new IllegalArgumentException(
+ "LockFreeExponentiallyDecayingReservoir size must be positive: " + value);
+ }
+ this.size = value;
+ return this;
+ }
+
+ /**
+ * Alpha is the exponential decay factor. Higher values bias results more heavily toward newer values.
+ */
+ public Builder alpha(double value) {
+ this.alpha = value;
+ return this;
+ }
+
+ /**
+ * Interval at which this reservoir is rescaled.
+ */
+ public Builder rescaleThreshold(Duration value) {
+ this.rescaleThreshold = Objects.requireNonNull(value, "rescaleThreshold is required");
+ return this;
+ }
+
+ /**
+ * Clock instance used for decay.
+ */
+ public Builder clock(Clock value) {
+ this.clock = Objects.requireNonNull(value, "clock is required");
+ return this;
+ }
+
+ public Reservoir build() {
+ return new LockFreeExponentiallyDecayingReservoir(size, alpha, rescaleThreshold, clock);
+ }
+ }
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/LongAdder.java b/metrics-core/src/main/java/com/codahale/metrics/LongAdder.java
deleted file mode 100644
index 5299ee9..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/LongAdder.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Written by Doug Lea with assistance from members of JCP JSR-166
- * Expert Group and released to the public domain, as explained at
- * http://creativecommons.org/publicdomain/zero/1.0/
- *
- * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.14&view=markup
- */
-
-package com.codahale.metrics;
-
-import java.io.Serializable;
-import java.util.concurrent.atomic.AtomicLong;
-
-// CHECKSTYLE:OFF
-/**
- * One or more variables that together maintain an initially zero {@code long} sum. When updates
- * (method {@link #add}) are contended across threads, the set of variables may grow dynamically to
- * reduce contention. Method {@link #sum} (or, equivalently, {@link #longValue}) returns the current
- * total combined across the variables maintaining the sum.
- * <p/>
- * <p>This class is usually preferable to {@link AtomicLong} when multiple threads update a common
- * sum that is used for purposes such as collecting statistics, not for fine-grained synchronization
- * control. Under low update contention, the two classes have similar characteristics. But under
- * high contention, expected throughput of this class is significantly higher, at the expense of
- * higher space consumption.
- * <p/>
- * <p>This class extends {@link Number}, but does <em>not</em> define methods such as {@code
- * equals}, {@code hashCode} and {@code compareTo} because instances are expected to be mutated, and
- * so are not useful as collection keys.
- * <p/>
- * <p><em>jsr166e note: This class is targeted to be placed in java.util.concurrent.atomic.</em>
- *
- * @author Doug Lea
- * @since 1.8
- */
-@SuppressWarnings("all")
-class LongAdder extends Striped64 implements Serializable {
- private static final long serialVersionUID = 7249069246863182397L;
-
- /**
- * Version of plus for use in retryUpdate
- */
- final long fn(long v, long x) {
- return v + x;
- }
-
- /**
- * Creates a new adder with initial sum of zero.
- */
- LongAdder() {
- }
-
- /**
- * Adds the given value.
- *
- * @param x the value to add
- */
- public void add(long x) {
- Cell[] as;
- long b, v;
- HashCode hc;
- Cell a;
- int n;
- if ((as = cells) != null || !casBase(b = base, b + x)) {
- boolean uncontended = true;
- int h = (hc = threadHashCode.get()).code;
- if (as == null || (n = as.length) < 1 ||
- (a = as[(n - 1) & h]) == null ||
- !(uncontended = a.cas(v = a.value, v + x)))
- retryUpdate(x, hc, uncontended);
- }
- }
-
- /**
- * Equivalent to {@code add(1)}.
- */
- public void increment() {
- add(1L);
- }
-
- /**
- * Equivalent to {@code add(-1)}.
- */
- public void decrement() {
- add(-1L);
- }
-
- /**
- * Returns the current sum. The returned value is <em>NOT</em> an atomic snapshot; invocation
- * in the absence of concurrent updates returns an accurate result, but concurrent updates that
- * occur while the sum is being calculated might not be incorporated.
- *
- * @return the sum
- */
- public long sum() {
- long sum = base;
- Cell[] as = cells;
- if (as != null) {
- int n = as.length;
- for (int i = 0; i < n; ++i) {
- Cell a = as[i];
- if (a != null)
- sum += a.value;
- }
- }
- return sum;
- }
-
- /**
- * Resets variables maintaining the sum to zero. This method may be a useful alternative to
- * creating a new adder, but is only effective if there are no concurrent updates. Because this
- * method is intrinsically racy, it should only be used when it is known that no threads are
- * concurrently updating.
- */
- public void reset() {
- internalReset(0L);
- }
-
- /**
- * Equivalent in effect to {@link #sum} followed by {@link #reset}. This method may apply for
- * example during quiescent points between multithreaded computations. If there are updates
- * concurrent with this method, the returned value is <em>not</em> guaranteed to be the final
- * value occurring before the reset.
- *
- * @return the sum
- */
- public long sumThenReset() {
- long sum = base;
- Cell[] as = cells;
- base = 0L;
- if (as != null) {
- int n = as.length;
- for (int i = 0; i < n; ++i) {
- Cell a = as[i];
- if (a != null) {
- sum += a.value;
- a.value = 0L;
- }
- }
- }
- return sum;
- }
-
- /**
- * Returns the String representation of the {@link #sum}.
- *
- * @return the String representation of the {@link #sum}
- */
- public String toString() {
- return Long.toString(sum());
- }
-
- /**
- * Equivalent to {@link #sum}.
- *
- * @return the sum
- */
- public long longValue() {
- return sum();
- }
-
- /**
- * Returns the {@link #sum} as an {@code int} after a narrowing primitive conversion.
- */
- public int intValue() {
- return (int) sum();
- }
-
- /**
- * Returns the {@link #sum} as a {@code float} after a widening primitive conversion.
- */
- public float floatValue() {
- return (float) sum();
- }
-
- /**
- * Returns the {@link #sum} as a {@code double} after a widening primitive conversion.
- */
- public double doubleValue() {
- return (double) sum();
- }
-
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- s.defaultWriteObject();
- s.writeLong(sum());
- }
-
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
- busy = 0;
- cells = null;
- base = s.readLong();
- }
-}
-// CHECKSTYLE:ON
diff --git a/metrics-core/src/main/java/com/codahale/metrics/LongAdderAdapter.java b/metrics-core/src/main/java/com/codahale/metrics/LongAdderAdapter.java
deleted file mode 100644
index d38febd..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/LongAdderAdapter.java
+++ /dev/null
@@ -1,18 +0,0 @@
-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
deleted file mode 100644
index bec8ab9..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/LongAdderProxy.java
+++ /dev/null
@@ -1,108 +0,0 @@
-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 1f8b80b..c153bfa 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Meter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Meter.java
@@ -1,26 +1,30 @@
package com.codahale.metrics;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.LongAdder;
/**
* A meter metric which measures mean throughput and one-, five-, and fifteen-minute
- * exponentially-weighted moving average throughputs.
+ * moving average throughputs.
*
- * @see EWMA
+ * @see MovingAverages
*/
public class Meter implements Metered {
- private static final long TICK_INTERVAL = TimeUnit.SECONDS.toNanos(5);
- private final EWMA m1Rate = EWMA.oneMinuteEWMA();
- private final EWMA m5Rate = EWMA.fiveMinuteEWMA();
- private final EWMA m15Rate = EWMA.fifteenMinuteEWMA();
-
- private final LongAdderAdapter count = LongAdderProxy.create();
+ private final MovingAverages movingAverages;
+ private final LongAdder count = new LongAdder();
private final long startTime;
- private final AtomicLong lastTick;
private final Clock clock;
+ /**
+ * Creates a new {@link Meter}.
+ *
+ * @param movingAverages the {@link MovingAverages} implementation to use
+ */
+ public Meter(MovingAverages movingAverages) {
+ this(movingAverages, Clock.defaultClock());
+ }
+
/**
* Creates a new {@link Meter}.
*/
@@ -31,12 +35,22 @@ public class Meter implements Metered {
/**
* Creates a new {@link Meter}.
*
- * @param clock the clock to use for the meter ticks
+ * @param clock the clock to use for the meter ticks
*/
public Meter(Clock clock) {
+ this(new ExponentialMovingAverages(clock), clock);
+ }
+
+ /**
+ * Creates a new {@link Meter}.
+ *
+ * @param movingAverages the {@link MovingAverages} implementation to use
+ * @param clock the clock to use for the meter ticks
+ */
+ public Meter(MovingAverages movingAverages, Clock clock) {
+ this.movingAverages = movingAverages;
this.clock = clock;
this.startTime = this.clock.getTick();
- this.lastTick = new AtomicLong(startTime);
}
/**
@@ -52,28 +66,9 @@ public class Meter implements Metered {
* @param n the number of events
*/
public void mark(long n) {
- tickIfNecessary();
+ movingAverages.tickIfNecessary();
count.add(n);
- m1Rate.update(n);
- m5Rate.update(n);
- m15Rate.update(n);
- }
-
- private void tickIfNecessary() {
- final long oldTick = lastTick.get();
- final long newTick = clock.getTick();
- final long age = newTick - oldTick;
- if (age > TICK_INTERVAL) {
- final long newIntervalStartTick = newTick - age % TICK_INTERVAL;
- if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) {
- final long requiredTicks = age / TICK_INTERVAL;
- for (long i = 0; i < requiredTicks; i++) {
- m1Rate.tick();
- m5Rate.tick();
- m15Rate.tick();
- }
- }
- }
+ movingAverages.update(n);
}
@Override
@@ -83,14 +78,14 @@ public class Meter implements Metered {
@Override
public double getFifteenMinuteRate() {
- tickIfNecessary();
- return m15Rate.getRate(TimeUnit.SECONDS);
+ movingAverages.tickIfNecessary();
+ return movingAverages.getM15Rate();
}
@Override
public double getFiveMinuteRate() {
- tickIfNecessary();
- return m5Rate.getRate(TimeUnit.SECONDS);
+ movingAverages.tickIfNecessary();
+ return movingAverages.getM5Rate();
}
@Override
@@ -98,14 +93,14 @@ public class Meter implements Metered {
if (getCount() == 0) {
return 0.0;
} else {
- final double elapsed = (clock.getTick() - startTime);
+ final double elapsed = clock.getTick() - startTime;
return getCount() / elapsed * TimeUnit.SECONDS.toNanos(1);
}
}
@Override
public double getOneMinuteRate() {
- tickIfNecessary();
- return m1Rate.getRate(TimeUnit.SECONDS);
+ movingAverages.tickIfNecessary();
+ return movingAverages.getM1Rate();
}
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Metered.java b/metrics-core/src/main/java/com/codahale/metrics/Metered.java
index 48f2ed0..e3b4283 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Metered.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Metered.java
@@ -1,7 +1,7 @@
package com.codahale.metrics;
/**
- * An object which maintains mean and exponentially-weighted rate.
+ * An object which maintains mean and moving average rates.
*/
public interface Metered extends Metric, Counting {
/**
@@ -9,29 +9,24 @@ public interface Metered extends Metric, Counting {
*
* @return the number of events which have been marked
*/
+ @Override
long getCount();
/**
- * Returns the fifteen-minute exponentially-weighted moving average rate at which events have
+ * Returns the fifteen-minute moving average rate at which events have
* occurred since the meter was created.
- * <p/>
- * This rate has the same exponential decay factor as the fifteen-minute load average in the
- * {@code top} Unix command.
*
- * @return the fifteen-minute exponentially-weighted moving average rate at which events have
- * occurred since the meter was created
+ * @return the fifteen-minute moving average rate at which events have
+ * occurred since the meter was created
*/
double getFifteenMinuteRate();
/**
- * Returns the five-minute exponentially-weighted moving average rate at which events have
+ * Returns the five-minute moving average rate at which events have
* occurred since the meter was created.
- * <p/>
- * This rate has the same exponential decay factor as the five-minute load average in the {@code
- * top} Unix command.
*
- * @return the five-minute exponentially-weighted moving average rate at which events have
- * occurred since the meter was created
+ * @return the five-minute moving average rate at which events have
+ * occurred since the meter was created
*/
double getFiveMinuteRate();
@@ -43,14 +38,11 @@ public interface Metered extends Metric, Counting {
double getMeanRate();
/**
- * Returns the one-minute exponentially-weighted moving average rate at which events have
+ * Returns the one-minute moving average rate at which events have
* occurred since the meter was created.
- * <p/>
- * This rate has the same exponential decay factor as the one-minute load average in the {@code
- * top} Unix command.
*
- * @return the one-minute exponentially-weighted moving average rate at which events have
- * occurred since the meter was created
+ * @return the one-minute moving average rate at which events have
+ * occurred since the meter was created
*/
double getOneMinuteRate();
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/MetricFilter.java b/metrics-core/src/main/java/com/codahale/metrics/MetricFilter.java
index 1239046..bb84b8d 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/MetricFilter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/MetricFilter.java
@@ -7,18 +7,25 @@ public interface MetricFilter {
/**
* Matches all metrics, regardless of type or name.
*/
- MetricFilter ALL = new MetricFilter() {
- @Override
- public boolean matches(String name, Metric metric) {
- return true;
- }
- };
+ MetricFilter ALL = (name, metric) -> true;
+
+ static MetricFilter startsWith(String prefix) {
+ return (name, metric) -> name.startsWith(prefix);
+ }
+
+ static MetricFilter endsWith(String suffix) {
+ return (name, metric) -> name.endsWith(suffix);
+ }
+
+ static MetricFilter contains(String substring) {
+ return (name, metric) -> name.contains(substring);
+ }
/**
* Returns {@code true} if the metric matches the filter; {@code false} otherwise.
*
- * @param name the metric's name
- * @param metric the metric
+ * @param name the metric's name
+ * @param metric the metric
* @return {@code true} if the metric matches the filter
*/
boolean matches(String name, Metric metric);
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 8662954..528ed2b 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/MetricRegistry.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/MetricRegistry.java
@@ -1,6 +1,12 @@
package com.codahale.metrics;
-import java.util.*;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -12,8 +18,8 @@ public class MetricRegistry implements MetricSet {
/**
* Concatenates elements to form a dotted name, eliding any null values or empty strings.
*
- * @param name the first element of the name
- * @param names the remaining elements of the name
+ * @param name the first element of the name
+ * @param names the remaining elements of the name
* @return {@code name} and {@code names} concatenated by periods
*/
public static String name(String name, String... names) {
@@ -31,8 +37,8 @@ public class MetricRegistry implements MetricSet {
* Concatenates a class name and elements to form a dotted name, eliding any null values or
* empty strings.
*
- * @param klass the first element of the name
- * @param names the remaining elements of the name
+ * @param klass the first element of the name
+ * @param names the remaining elements of the name
* @return {@code klass} and {@code names} concatenated by periods
*/
public static String name(Class<?> klass, String... names) {
@@ -56,7 +62,7 @@ public class MetricRegistry implements MetricSet {
*/
public MetricRegistry() {
this.metrics = buildMap();
- this.listeners = new CopyOnWriteArrayList<MetricRegistryListener>();
+ this.listeners = new CopyOnWriteArrayList<>();
}
/**
@@ -67,7 +73,19 @@ public class MetricRegistry implements MetricSet {
* @return a new {@link ConcurrentMap}
*/
protected ConcurrentMap<String, Metric> buildMap() {
- return new ConcurrentHashMap<String, Metric>();
+ return new ConcurrentHashMap<>();
+ }
+
+ /**
+ * Given a {@link Gauge}, registers it under the given name and returns it
+ *
+ * @param name the name of the gauge
+ * @param <T> the type of the gauge's value
+ * @return the registered {@link Gauge}
+ * @since 4.2.10
+ */
+ public <T> Gauge<T> registerGauge(String name, Gauge<T> metric) throws IllegalArgumentException {
+ return register(name, metric);
}
/**
@@ -77,11 +95,70 @@ public class MetricRegistry implements MetricSet {
* @param metric the metric
* @param <T> the type of the metric
* @return {@code metric}
- * @throws IllegalArgumentException if the name is already registered
+ * @throws IllegalArgumentException if the name is already registered or metric variable is null
*/
@SuppressWarnings("unchecked")
public <T extends Metric> T register(String name, T metric) throws IllegalArgumentException {
- if (metric instanceof MetricSet) {
+
+ if (metric == null) {
+ throw new NullPointerException("metric == null");
+ }
+
+ if (metric instanceof MetricRegistry) {
+ final MetricRegistry childRegistry = (MetricRegistry) metric;
+ final String childName = name;
+ childRegistry.addListener(new MetricRegistryListener() {
+ @Override
+ public void onGaugeAdded(String name, Gauge<?> gauge) {
+ register(name(childName, name), gauge);
+ }
+
+ @Override
+ public void onGaugeRemoved(String name) {
+ remove(name(childName, name));
+ }
+
+ @Override
+ public void onCounterAdded(String name, Counter counter) {
+ register(name(childName, name), counter);
+ }
+
+ @Override
+ public void onCounterRemoved(String name) {
+ remove(name(childName, name));
+ }
+
+ @Override
+ public void onHistogramAdded(String name, Histogram histogram) {
+ register(name(childName, name), histogram);
+ }
+
+ @Override
+ public void onHistogramRemoved(String name) {
+ remove(name(childName, name));
+ }
+
+ @Override
+ public void onMeterAdded(String name, Meter meter) {
+ register(name(childName, name), meter);
+ }
+
+ @Override
+ public void onMeterRemoved(String name) {
+ remove(name(childName, name));
+ }
+
+ @Override
+ public void onTimerAdded(String name, Timer timer) {
+ register(name(childName, name), timer);
+ }
+
+ @Override
+ public void onTimerRemoved(String name) {
+ remove(name(childName, name));
+ }
+ });
+ } else if (metric instanceof MetricSet) {
registerAll(name, (MetricSet) metric);
} else {
final Metric existing = metrics.putIfAbsent(name, metric);
@@ -97,7 +174,7 @@ public class MetricRegistry implements MetricSet {
/**
* Given a metric set, registers them.
*
- * @param metrics a set of metrics
+ * @param metrics a set of metrics
* @throws IllegalArgumentException if any of the names are already registered
*/
public void registerAll(MetricSet metrics) throws IllegalArgumentException {
@@ -105,7 +182,7 @@ public class MetricRegistry implements MetricSet {
}
/**
- * Return the {@link Counter} registered under this name; or create and register
+ * Return the {@link Counter} registered under this name; or create and register
* a new {@link Counter} if none is registered.
*
* @param name the name of the metric
@@ -119,7 +196,7 @@ 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 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}
*/
@@ -129,6 +206,7 @@ public class MetricRegistry implements MetricSet {
public Counter newMetric() {
return supplier.newMetric();
}
+
@Override
public boolean isInstance(Metric metric) {
return Counter.class.isInstance(metric);
@@ -137,7 +215,7 @@ public class MetricRegistry implements MetricSet {
}
/**
- * Return the {@link Histogram} registered under this name; or create and register
+ * Return the {@link Histogram} registered under this name; or create and register
* a new {@link Histogram} if none is registered.
*
* @param name the name of the metric
@@ -151,21 +229,22 @@ public class MetricRegistry implements MetricSet {
* 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 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 getOrAdd(name, new MetricBuilder<Histogram>() {
+ @Override
+ public Histogram newMetric() {
+ return supplier.newMetric();
+ }
+
+ @Override
+ public boolean isInstance(Metric metric) {
+ return Histogram.class.isInstance(metric);
+ }
+ });
}
/**
@@ -183,7 +262,7 @@ public class MetricRegistry implements MetricSet {
* 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 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}
*/
@@ -193,6 +272,7 @@ public class MetricRegistry implements MetricSet {
public Meter newMetric() {
return supplier.newMetric();
}
+
@Override
public boolean isInstance(Metric metric) {
return Meter.class.isInstance(metric);
@@ -215,7 +295,7 @@ public class MetricRegistry implements MetricSet {
* 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 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}
*/
@@ -225,6 +305,7 @@ public class MetricRegistry implements MetricSet {
public Timer newMetric() {
return supplier.newMetric();
}
+
@Override
public boolean isInstance(Metric metric) {
return Timer.class.isInstance(metric);
@@ -234,18 +315,33 @@ public class MetricRegistry implements MetricSet {
/**
* Return the {@link Gauge} registered under this name; or create and register
- * a new {@link Gauge} using the provided MetricSupplier if none is registered.
+ * a new {@link SettableGauge} if none is registered.
*
* @param name the name of the metric
+ * @return a pre-existing {@link Gauge} or a new {@link SettableGauge}
+ * @since 4.2
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public <T extends Gauge> T gauge(String name) {
+ return (T) getOrAdd(name, MetricBuilder.GAUGES);
+ }
+
+ /**
+ * 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
* @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>() {
+ @SuppressWarnings("rawtypes")
+ public <T extends Gauge> T gauge(String name, final MetricSupplier<T> supplier) {
+ return getOrAdd(name, new MetricBuilder<T>() {
@Override
- public Gauge newMetric() {
+ public T newMetric() {
return supplier.newMetric();
}
+
@Override
public boolean isInstance(Metric metric) {
return Gauge.class.isInstance(metric);
@@ -254,12 +350,12 @@ public class MetricRegistry implements MetricSet {
}
- /**
- * Removes the metric with the given name.
- *
- * @param name the name of the metric
- * @return whether or not the metric was removed
- */
+ /**
+ * 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) {
@@ -285,7 +381,7 @@ public class MetricRegistry implements MetricSet {
/**
* Adds a {@link MetricRegistryListener} to a collection of listeners that will be notified on
* metric creation. Listeners will be notified in the order in which they are added.
- * <p/>
+ * <p>
* <b>N.B.:</b> The listener will be notified of all existing metrics when it first registers.
*
* @param listener the listener that will be notified
@@ -313,7 +409,7 @@ public class MetricRegistry implements MetricSet {
* @return the names of all the metrics
*/
public SortedSet<String> getNames() {
- return Collections.unmodifiableSortedSet(new TreeSet<String>(metrics.keySet()));
+ return Collections.unmodifiableSortedSet(new TreeSet<>(metrics.keySet()));
}
/**
@@ -321,6 +417,7 @@ public class MetricRegistry implements MetricSet {
*
* @return all the gauges in the registry
*/
+ @SuppressWarnings("rawtypes")
public SortedMap<String, Gauge> getGauges() {
return getGauges(MetricFilter.ALL);
}
@@ -328,9 +425,10 @@ public class MetricRegistry implements MetricSet {
/**
* Returns a map of all the gauges in the registry and their names which match the given filter.
*
- * @param filter the metric filter to match
+ * @param filter the metric filter to match
* @return all the gauges in the registry
*/
+ @SuppressWarnings("rawtypes")
public SortedMap<String, Gauge> getGauges(MetricFilter filter) {
return getMetrics(Gauge.class, filter);
}
@@ -348,7 +446,7 @@ public class MetricRegistry implements MetricSet {
* Returns a map of all the counters in the registry and their names which match the given
* filter.
*
- * @param filter the metric filter to match
+ * @param filter the metric filter to match
* @return all the counters in the registry
*/
public SortedMap<String, Counter> getCounters(MetricFilter filter) {
@@ -368,7 +466,7 @@ public class MetricRegistry implements MetricSet {
* Returns a map of all the histograms in the registry and their names which match the given
* filter.
*
- * @param filter the metric filter to match
+ * @param filter the metric filter to match
* @return all the histograms in the registry
*/
public SortedMap<String, Histogram> getHistograms(MetricFilter filter) {
@@ -387,7 +485,7 @@ public class MetricRegistry implements MetricSet {
/**
* Returns a map of all the meters in the registry and their names which match the given filter.
*
- * @param filter the metric filter to match
+ * @param filter the metric filter to match
* @return all the meters in the registry
*/
public SortedMap<String, Meter> getMeters(MetricFilter filter) {
@@ -406,7 +504,7 @@ public class MetricRegistry implements MetricSet {
/**
* Returns a map of all the timers in the registry and their names which match the given filter.
*
- * @param filter the metric filter to match
+ * @param filter the metric filter to match
* @return all the timers in the registry
*/
public SortedMap<String, Timer> getTimers(MetricFilter filter) {
@@ -433,10 +531,10 @@ public class MetricRegistry implements MetricSet {
@SuppressWarnings("unchecked")
private <T extends Metric> SortedMap<String, T> getMetrics(Class<T> klass, MetricFilter filter) {
- final TreeMap<String, T> timers = new TreeMap<String, T>();
+ final TreeMap<String, T> timers = new TreeMap<>();
for (Map.Entry<String, Metric> entry : metrics.entrySet()) {
if (klass.isInstance(entry.getValue()) && filter.matches(entry.getKey(),
- entry.getValue())) {
+ entry.getValue())) {
timers.put(entry.getKey(), (T) entry.getValue());
}
}
@@ -487,7 +585,14 @@ public class MetricRegistry implements MetricSet {
}
}
- private void registerAll(String prefix, MetricSet metrics) throws IllegalArgumentException {
+ /**
+ * Given a metric set, registers them with the given prefix prepended to their names.
+ *
+ * @param prefix a name prefix
+ * @param metrics a set of metrics
+ * @throws IllegalArgumentException if any of the names are already registered
+ */
+ public void registerAll(String prefix, MetricSet metrics) throws IllegalArgumentException {
for (Map.Entry<String, Metric> entry : metrics.getMetrics().entrySet()) {
if (entry.getValue() instanceof MetricSet) {
registerAll(name(prefix, entry.getKey()), (MetricSet) entry.getValue());
@@ -502,8 +607,9 @@ public class MetricRegistry implements MetricSet {
return Collections.unmodifiableMap(metrics);
}
+ @FunctionalInterface
public interface MetricSupplier<T extends Metric> {
- T newMetric();
+ T newMetric();
}
/**
@@ -558,6 +664,19 @@ public class MetricRegistry implements MetricSet {
}
};
+ @SuppressWarnings("rawtypes")
+ MetricBuilder<Gauge> GAUGES = new MetricBuilder<Gauge>() {
+ @Override
+ public Gauge newMetric() {
+ return new DefaultSettableGauge<>();
+ }
+
+ @Override
+ public boolean isInstance(Metric metric) {
+ return Gauge.class.isInstance(metric);
+ }
+ };
+
T newMetric();
boolean isInstance(Metric metric);
diff --git a/metrics-core/src/main/java/com/codahale/metrics/MovingAverages.java b/metrics-core/src/main/java/com/codahale/metrics/MovingAverages.java
new file mode 100644
index 0000000..a0aee40
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/MovingAverages.java
@@ -0,0 +1,48 @@
+package com.codahale.metrics;
+
+/**
+ * A triple of moving averages (one-, five-, and fifteen-minute
+ * moving average) as needed by {@link Meter}.
+ * <p>
+ * Included implementations are:
+ * <ul>
+ * <li>{@link ExponentialMovingAverages} exponential decaying average similar to the {@code top} Unix command.
+ * <li>{@link SlidingTimeWindowMovingAverages} simple (unweighted) moving average
+ * </ul>
+ */
+public interface MovingAverages {
+
+ /**
+ * Tick the internal clock of the MovingAverages implementation if needed
+ * (according to the internal ticking interval)
+ */
+ void tickIfNecessary();
+
+ /**
+ * Update all three moving averages with n events having occurred since the last update.
+ *
+ * @param n
+ */
+ void update(long n);
+
+ /**
+ * Returns the one-minute moving average rate
+ *
+ * @return the one-minute moving average rate
+ */
+ double getM1Rate();
+
+ /**
+ * Returns the five-minute moving average rate
+ *
+ * @return the five-minute moving average rate
+ */
+ double getM5Rate();
+
+ /**
+ * Returns the fifteen-minute moving average rate
+ *
+ * @return the fifteen-minute moving average rate
+ */
+ double getM15Rate();
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/NoopMetricRegistry.java b/metrics-core/src/main/java/com/codahale/metrics/NoopMetricRegistry.java
new file mode 100644
index 0000000..db65193
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/NoopMetricRegistry.java
@@ -0,0 +1,793 @@
+package com.codahale.metrics;
+
+import java.io.OutputStream;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * A registry of metric instances which never creates or registers any metrics and returns no-op implementations of any metric type.
+ *
+ * @since 4.1.17
+ */
+public final class NoopMetricRegistry extends MetricRegistry {
+ private static final EmptyConcurrentMap<String, Metric> EMPTY_CONCURRENT_MAP = new EmptyConcurrentMap<>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected ConcurrentMap<String, Metric> buildMap() {
+ return EMPTY_CONCURRENT_MAP;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public <T extends Metric> T register(String name, T metric) throws IllegalArgumentException {
+ if (metric == null) {
+ throw new NullPointerException("metric == null");
+ }
+ return metric;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void registerAll(MetricSet metrics) throws IllegalArgumentException {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Counter counter(String name) {
+ return NoopCounter.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Counter counter(String name, MetricSupplier<Counter> supplier) {
+ return NoopCounter.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Histogram histogram(String name) {
+ return NoopHistogram.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Histogram histogram(String name, MetricSupplier<Histogram> supplier) {
+ return NoopHistogram.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Meter meter(String name) {
+ return NoopMeter.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Meter meter(String name, MetricSupplier<Meter> supplier) {
+ return NoopMeter.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Timer timer(String name) {
+ return NoopTimer.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Timer timer(String name, MetricSupplier<Timer> supplier) {
+ return NoopTimer.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 4.2
+ */
+ @Override
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public <T extends Gauge> T gauge(String name) {
+ return (T) NoopGauge.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public <T extends Gauge> T gauge(String name, MetricSupplier<T> supplier) {
+ return (T) NoopGauge.INSTANCE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean remove(String name) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void removeMatching(MetricFilter filter) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addListener(MetricRegistryListener listener) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void removeListener(MetricRegistryListener listener) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedSet<String> getNames() {
+ return Collections.emptySortedSet();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("rawtypes")
+ public SortedMap<String, Gauge> getGauges() {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("rawtypes")
+ public SortedMap<String, Gauge> getGauges(MetricFilter filter) {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedMap<String, Counter> getCounters() {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedMap<String, Counter> getCounters(MetricFilter filter) {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedMap<String, Histogram> getHistograms() {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedMap<String, Histogram> getHistograms(MetricFilter filter) {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedMap<String, Meter> getMeters() {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedMap<String, Meter> getMeters(MetricFilter filter) {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedMap<String, Timer> getTimers() {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SortedMap<String, Timer> getTimers(MetricFilter filter) {
+ return Collections.emptySortedMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void registerAll(String prefix, MetricSet metrics) throws IllegalArgumentException {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Map<String, Metric> getMetrics() {
+ return Collections.emptyMap();
+ }
+
+ static final class NoopGauge<T> implements Gauge<T> {
+ private static final NoopGauge<?> INSTANCE = new NoopGauge<>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T getValue() {
+ return null;
+ }
+ }
+
+ private static final class EmptySnapshot extends Snapshot {
+ private static final EmptySnapshot INSTANCE = new EmptySnapshot();
+ private static final long[] EMPTY_LONG_ARRAY = new long[0];
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getValue(double quantile) {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long[] getValues() {
+ return EMPTY_LONG_ARRAY;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getMax() {
+ return 0L;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getMean() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getMin() {
+ return 0L;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getStdDev() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dump(OutputStream output) {
+ // NOP
+ }
+ }
+
+ static final class NoopTimer extends Timer {
+ private static final NoopTimer INSTANCE = new NoopTimer();
+ private static final Timer.Context CONTEXT = new NoopTimer.Context();
+
+ private static class Context extends Timer.Context {
+ private static final Clock CLOCK = new Clock() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getTick() {
+ return 0L;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getTime() {
+ return 0L;
+ }
+ };
+
+ private Context() {
+ super(INSTANCE, CLOCK);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long stop() {
+ return 0L;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() {
+ // NOP
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void update(long duration, TimeUnit unit) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void update(Duration duration) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public <T> T time(Callable<T> event) throws Exception {
+ return event.call();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public <T> T timeSupplier(Supplier<T> event) {
+ return event.get();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void time(Runnable event) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Timer.Context time() {
+ return CONTEXT;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getCount() {
+ return 0L;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getFifteenMinuteRate() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getFiveMinuteRate() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getMeanRate() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getOneMinuteRate() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Snapshot getSnapshot() {
+ return EmptySnapshot.INSTANCE;
+ }
+ }
+
+ static final class NoopHistogram extends Histogram {
+ private static final NoopHistogram INSTANCE = new NoopHistogram();
+ private static final Reservoir EMPTY_RESERVOIR = new Reservoir() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void update(long value) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Snapshot getSnapshot() {
+ return EmptySnapshot.INSTANCE;
+ }
+ };
+
+ private NoopHistogram() {
+ super(EMPTY_RESERVOIR);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void update(int value) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void update(long value) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getCount() {
+ return 0L;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Snapshot getSnapshot() {
+ return EmptySnapshot.INSTANCE;
+ }
+ }
+
+ static final class NoopCounter extends Counter {
+ private static final NoopCounter INSTANCE = new NoopCounter();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void inc() {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void inc(long n) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dec() {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dec(long n) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getCount() {
+ return 0L;
+ }
+ }
+
+ static final class NoopMeter extends Meter {
+ private static final NoopMeter INSTANCE = new NoopMeter();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void mark() {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void mark(long n) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getCount() {
+ return 0L;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getFifteenMinuteRate() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getFiveMinuteRate() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getMeanRate() {
+ return 0D;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getOneMinuteRate() {
+ return 0D;
+ }
+ }
+
+ private static final class EmptyConcurrentMap<K, V> implements ConcurrentMap<K, V> {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V putIfAbsent(K key, V value) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean remove(Object key, Object value) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V replace(K key, V value) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean containsKey(Object key) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean containsValue(Object value) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V get(Object key) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V put(K key, V value) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V remove(Object key) {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void clear() {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<K> keySet() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Collection<V> values() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ return Collections.emptySet();
+ }
+ }
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ObjectNameFactory.java b/metrics-core/src/main/java/com/codahale/metrics/ObjectNameFactory.java
deleted file mode 100644
index 5b28715..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/ObjectNameFactory.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.codahale.metrics;
-
-import javax.management.ObjectName;
-
-public interface ObjectNameFactory {
-
- ObjectName createName(String type, String domain, String name);
-}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/RatioGauge.java b/metrics-core/src/main/java/com/codahale/metrics/RatioGauge.java
index 182155f..b6407ab 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/RatioGauge.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/RatioGauge.java
@@ -5,7 +5,7 @@ import static java.lang.Double.isNaN;
/**
* A gauge which measures the ratio of one value to another.
- * <p/>
+ * <p>
* If the denominator is zero, not a number, or infinite, the resulting ratio is not a number.
*/
public abstract class RatioGauge implements Gauge<Double> {
@@ -16,8 +16,8 @@ public abstract class RatioGauge implements Gauge<Double> {
/**
* Creates a new ratio with the given numerator and denominator.
*
- * @param numerator the numerator of the ratio
- * @param denominator the denominator of the ratio
+ * @param numerator the numerator of the ratio
+ * @param denominator the denominator of the ratio
* @return {@code numerator:denominator}
*/
public static Ratio of(double numerator, double denominator) {
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Reporter.java b/metrics-core/src/main/java/com/codahale/metrics/Reporter.java
index a429408..cbee18a 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Reporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Reporter.java
@@ -1,8 +1,10 @@
package com.codahale.metrics;
+import java.io.Closeable;
+
/*
* A tag interface to indicate that a class is a Reporter.
*/
-public interface Reporter {
+public interface Reporter extends Closeable {
}
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 32e6757..4723cdf 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/ScheduledReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/ScheduledReporter.java
@@ -8,13 +8,12 @@ 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.Executor;
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;
/**
@@ -71,11 +70,11 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
/**
* 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 rateUnit a unit of time
+ * @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 rateUnit a unit of time
* @param durationUnit a unit of time
*/
protected ScheduledReporter(MetricRegistry registry,
@@ -83,7 +82,7 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
MetricFilter filter,
TimeUnit rateUnit,
TimeUnit durationUnit) {
- this(registry, name, filter, rateUnit, durationUnit, createDefaultExecutor(name));
+ this(registry, name, filter, rateUnit, durationUnit, createDefaultExecutor(name));
}
/**
@@ -107,11 +106,11 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
/**
* 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 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,
@@ -121,8 +120,7 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
TimeUnit durationUnit,
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop) {
- this(registry, name, filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,
- Collections.<MetricAttribute>emptySet());
+ this(registry, name, filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop, Collections.emptySet());
}
protected ScheduledReporter(MetricRegistry registry,
@@ -133,16 +131,21 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop,
Set<MetricAttribute> disabledMetricAttributes) {
+
+ if (registry == null) {
+ throw new NullPointerException("registry == null");
+ }
+
this.registry = registry;
this.filter = filter;
- this.executor = executor == null? createDefaultExecutor(name) : executor;
+ this.executor = executor == null ? createDefaultExecutor(name) : executor;
this.shutdownExecutorOnStop = shutdownExecutorOnStop;
this.rateFactor = rateUnit.toSeconds(1);
this.rateUnit = calculateRateUnit(rateUnit);
this.durationFactor = durationUnit.toNanos(1);
this.durationUnit = durationUnit.toString().toLowerCase(Locale.US);
this.disabledMetricAttributes = disabledMetricAttributes != null ? disabledMetricAttributes :
- Collections.<MetricAttribute>emptySet();
+ Collections.emptySet();
}
/**
@@ -152,7 +155,7 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
* @param unit the unit for {@code period}
*/
public void start(long period, TimeUnit unit) {
- start(period, period, unit);
+ start(period, period, unit);
}
/**
@@ -164,7 +167,30 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
throw new IllegalArgumentException("Reporter already started");
}
- this.scheduledFuture = executor.scheduleAtFixedRate(runnable, initialDelay, period, unit);
+ this.scheduledFuture = getScheduledFuture(initialDelay, period, unit, runnable);
+ }
+
+
+ /**
+ * Schedule the task, and return a future.
+ *
+ * @deprecated Use {@link #getScheduledFuture(long, long, TimeUnit, Runnable, ScheduledExecutorService)} instead.
+ */
+ @SuppressWarnings("DeprecatedIsStillUsed")
+ @Deprecated
+ protected ScheduledFuture<?> getScheduledFuture(long initialDelay, long period, TimeUnit unit, Runnable runnable) {
+ return getScheduledFuture(initialDelay, period, unit, runnable, this.executor);
+ }
+
+ /**
+ * Schedule the task, and return a future.
+ * The current implementation uses scheduleWithFixedDelay, replacing scheduleWithFixedRate. This avoids queueing issues, but may
+ * cause some reporters to skip metrics, as scheduleWithFixedDelay introduces a growing delta from the original start point.
+ *
+ * Overriding this in a subclass to revert to the old behavior is permitted.
+ */
+ protected ScheduledFuture<?> getScheduledFuture(long initialDelay, long period, TimeUnit unit, Runnable runnable, ScheduledExecutorService executor) {
+ return executor.scheduleWithFixedDelay(runnable, initialDelay, period, unit);
}
/**
@@ -172,36 +198,42 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
*
* @param initialDelay the time to delay the first execution
* @param period the amount of time between polls
- * @param unit the unit for {@code period}
+ * @param unit the unit for {@code period} and {@code initialDelay}
*/
synchronized public void start(long initialDelay, long period, TimeUnit unit) {
- start(initialDelay, period, unit, 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);
- }
+ start(initialDelay, period, unit, () -> {
+ try {
+ report();
+ } catch (Throwable ex) {
+ LOG.error("Exception thrown from {}#report. Exception was suppressed.", ScheduledReporter.this.getClass().getSimpleName(), ex);
}
});
}
/**
* Stops the reporter and if shutdownExecutorOnStop is true then shuts down its thread of execution.
- *
+ * <p>
* Uses the shutdown pattern from http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
*/
public void stop() {
if (shutdownExecutorOnStop) {
executor.shutdown(); // Disable new tasks from being submitted
+ }
+
+ try {
+ report(); // Report metrics one last time
+ } catch (Exception e) {
+ LOG.warn("Final reporting of metrics failed.", e);
+ }
+
+ if (shutdownExecutorOnStop) {
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 (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
- System.err.println(getClass().getSimpleName() + ": ScheduledExecutorService did not terminate");
+ LOG.warn("ScheduledExecutorService did not terminate.");
}
}
} catch (InterruptedException ie) {
@@ -211,20 +243,22 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
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);
- }
+ // The external manager (like JEE container) responsible for lifecycle of executor
+ cancelScheduledFuture();
+ }
+ }
+
+ private synchronized void cancelScheduledFuture() {
+ 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);
}
/**
@@ -257,6 +291,7 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
* @param meters all of the meters in the registry
* @param timers all of the timers in the registry
*/
+ @SuppressWarnings("rawtypes")
public abstract void report(SortedMap<String, Gauge> gauges,
SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms,
diff --git a/metrics-core/src/main/java/com/codahale/metrics/SettableGauge.java b/metrics-core/src/main/java/com/codahale/metrics/SettableGauge.java
new file mode 100644
index 0000000..68f18a8
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/SettableGauge.java
@@ -0,0 +1,14 @@
+package com.codahale.metrics;
+
+/**
+ * <p>
+ * Similar to {@link Gauge}, but metric value is updated via calling {@link #setValue(T)} instead.
+ * See {@link DefaultSettableGauge}.
+ * </p>
+ */
+public interface SettableGauge<T> extends Gauge<T> {
+ /**
+ * Set the metric to a new value.
+ */
+ void setValue(T value);
+}
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 2fe00ae..91bee56 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/SharedMetricRegistries.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/SharedMetricRegistries.java
@@ -10,9 +10,9 @@ import java.util.concurrent.atomic.AtomicReference;
*/
public class SharedMetricRegistries {
private static final ConcurrentMap<String, MetricRegistry> REGISTRIES =
- new ConcurrentHashMap<String, MetricRegistry>();
+ new ConcurrentHashMap<>();
- private static AtomicReference<String> defaultRegistryName = new AtomicReference<String>();
+ private static AtomicReference<String> defaultRegistryName = new AtomicReference<>();
/* Visible for testing */
static void setDefaultRegistryName(AtomicReference<String> defaultRegistryName) {
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 317517c..63c0bdc 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Slf4jReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Slf4jReporter.java
@@ -4,10 +4,29 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
+import java.util.Collections;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import static com.codahale.metrics.MetricAttribute.COUNT;
+import static com.codahale.metrics.MetricAttribute.M15_RATE;
+import static com.codahale.metrics.MetricAttribute.M1_RATE;
+import static com.codahale.metrics.MetricAttribute.M5_RATE;
+import static com.codahale.metrics.MetricAttribute.MAX;
+import static com.codahale.metrics.MetricAttribute.MEAN;
+import static com.codahale.metrics.MetricAttribute.MEAN_RATE;
+import static com.codahale.metrics.MetricAttribute.MIN;
+import static com.codahale.metrics.MetricAttribute.P50;
+import static com.codahale.metrics.MetricAttribute.P75;
+import static com.codahale.metrics.MetricAttribute.P95;
+import static com.codahale.metrics.MetricAttribute.P98;
+import static com.codahale.metrics.MetricAttribute.P99;
+import static com.codahale.metrics.MetricAttribute.P999;
+import static com.codahale.metrics.MetricAttribute.STDDEV;
/**
* A reporter class for logging metrics values to a SLF4J {@link Logger} periodically, similar to
@@ -26,7 +45,7 @@ public class Slf4jReporter extends ScheduledReporter {
return new Builder(registry);
}
- public enum LoggingLevel {TRACE, DEBUG, INFO, WARN, ERROR}
+ public enum LoggingLevel { TRACE, DEBUG, INFO, WARN, ERROR }
/**
* A builder for {@link Slf4jReporter} instances. Defaults to logging to {@code metrics}, not
@@ -44,6 +63,7 @@ public class Slf4jReporter extends ScheduledReporter {
private MetricFilter filter;
private ScheduledExecutorService executor;
private boolean shutdownExecutorOnStop;
+ private Set<MetricAttribute> disabledMetricAttributes;
private Builder(MetricRegistry registry) {
this.registry = registry;
@@ -56,6 +76,7 @@ public class Slf4jReporter extends ScheduledReporter {
this.loggingLevel = LoggingLevel.INFO;
this.executor = null;
this.shutdownExecutorOnStop = true;
+ this.disabledMetricAttributes = Collections.emptySet();
}
/**
@@ -161,6 +182,18 @@ public class Slf4jReporter extends ScheduledReporter {
return this;
}
+ /**
+ * Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15").
+ * See {@link MetricAttribute}.
+ *
+ * @param disabledMetricAttributes a set of {@link MetricAttribute}
+ * @return {@code this}
+ */
+ public Builder disabledMetricAttributes(Set<MetricAttribute> disabledMetricAttributes) {
+ this.disabledMetricAttributes = disabledMetricAttributes;
+ return this;
+ }
+
/**
* Builds a {@link Slf4jReporter} with the given properties.
*
@@ -186,7 +219,8 @@ public class Slf4jReporter extends ScheduledReporter {
loggerProxy = new DebugLoggerProxy(logger);
break;
}
- return new Slf4jReporter(registry, loggerProxy, marker, prefix, rateUnit, durationUnit, filter, executor, shutdownExecutorOnStop);
+ return new Slf4jReporter(registry, loggerProxy, marker, prefix, rateUnit, durationUnit, filter, executor,
+ shutdownExecutorOnStop, disabledMetricAttributes);
}
}
@@ -202,108 +236,174 @@ public class Slf4jReporter extends ScheduledReporter {
TimeUnit durationUnit,
MetricFilter filter,
ScheduledExecutorService executor,
- boolean shutdownExecutorOnStop) {
- super(registry, "logger-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
+ boolean shutdownExecutorOnStop,
+ Set<MetricAttribute> disabledMetricAttributes) {
+ super(registry, "logger-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,
+ disabledMetricAttributes);
this.loggerProxy = loggerProxy;
this.marker = marker;
this.prefix = prefix;
}
@Override
+ @SuppressWarnings("rawtypes")
public void report(SortedMap<String, Gauge> gauges,
SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms,
SortedMap<String, Meter> meters,
SortedMap<String, Timer> timers) {
if (loggerProxy.isEnabled(marker)) {
+ StringBuilder b = new StringBuilder();
for (Entry<String, Gauge> entry : gauges.entrySet()) {
- logGauge(entry.getKey(), entry.getValue());
+ logGauge(b, entry.getKey(), entry.getValue());
}
for (Entry<String, Counter> entry : counters.entrySet()) {
- logCounter(entry.getKey(), entry.getValue());
+ logCounter(b, entry.getKey(), entry.getValue());
}
for (Entry<String, Histogram> entry : histograms.entrySet()) {
- logHistogram(entry.getKey(), entry.getValue());
+ logHistogram(b, entry.getKey(), entry.getValue());
}
for (Entry<String, Meter> entry : meters.entrySet()) {
- logMeter(entry.getKey(), entry.getValue());
+ logMeter(b, entry.getKey(), entry.getValue());
}
for (Entry<String, Timer> entry : timers.entrySet()) {
- logTimer(entry.getKey(), entry.getValue());
+ logTimer(b, entry.getKey(), entry.getValue());
}
}
}
- private void logTimer(String name, Timer timer) {
+ private void logTimer(StringBuilder b, String name, Timer timer) {
final Snapshot snapshot = timer.getSnapshot();
- loggerProxy.log(marker,
- "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()),
- convertDuration(snapshot.getMax()),
- convertDuration(snapshot.getMean()),
- convertDuration(snapshot.getStdDev()),
- convertDuration(snapshot.getMedian()),
- convertDuration(snapshot.get75thPercentile()),
- convertDuration(snapshot.get95thPercentile()),
- convertDuration(snapshot.get98thPercentile()),
- convertDuration(snapshot.get99thPercentile()),
- convertDuration(snapshot.get999thPercentile()),
- convertRate(timer.getMeanRate()),
- convertRate(timer.getOneMinuteRate()),
- convertRate(timer.getFiveMinuteRate()),
- convertRate(timer.getFifteenMinuteRate()),
- getRateUnit(),
- getDurationUnit());
+ b.setLength(0);
+ b.append("type=TIMER");
+ append(b, "name", prefix(name));
+ appendCountIfEnabled(b, timer);
+ appendLongDurationIfEnabled(b, MIN, snapshot::getMin);
+ appendLongDurationIfEnabled(b, MAX, snapshot::getMax);
+ appendDoubleDurationIfEnabled(b, MEAN, snapshot::getMean);
+ appendDoubleDurationIfEnabled(b, STDDEV, snapshot::getStdDev);
+ appendDoubleDurationIfEnabled(b, P50, snapshot::getMedian);
+ appendDoubleDurationIfEnabled(b, P75, snapshot::get75thPercentile);
+ appendDoubleDurationIfEnabled(b, P95, snapshot::get95thPercentile);
+ appendDoubleDurationIfEnabled(b, P98, snapshot::get98thPercentile);
+ appendDoubleDurationIfEnabled(b, P99, snapshot::get99thPercentile);
+ appendDoubleDurationIfEnabled(b, P999, snapshot::get999thPercentile);
+ appendMetered(b, timer);
+ append(b, "rate_unit", getRateUnit());
+ append(b, "duration_unit", getDurationUnit());
+ loggerProxy.log(marker, b.toString());
}
- private void logMeter(String name, Meter meter) {
- loggerProxy.log(marker,
- "type={}, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
- "METER",
- prefix(name),
- meter.getCount(),
- convertRate(meter.getMeanRate()),
- convertRate(meter.getOneMinuteRate()),
- convertRate(meter.getFiveMinuteRate()),
- convertRate(meter.getFifteenMinuteRate()),
- getRateUnit());
+ private void logMeter(StringBuilder b, String name, Meter meter) {
+ b.setLength(0);
+ b.append("type=METER");
+ append(b, "name", prefix(name));
+ appendCountIfEnabled(b, meter);
+ appendMetered(b, meter);
+ append(b, "rate_unit", getRateUnit());
+ loggerProxy.log(marker, b.toString());
}
- private void logHistogram(String name, Histogram histogram) {
+ private void logHistogram(StringBuilder b, String name, Histogram histogram) {
final Snapshot snapshot = histogram.getSnapshot();
- loggerProxy.log(marker,
- "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, " +
- "median={}, p75={}, p95={}, p98={}, p99={}, p999={}",
- "HISTOGRAM",
- prefix(name),
- histogram.getCount(),
- snapshot.getMin(),
- snapshot.getMax(),
- snapshot.getMean(),
- snapshot.getStdDev(),
- snapshot.getMedian(),
- snapshot.get75thPercentile(),
- snapshot.get95thPercentile(),
- snapshot.get98thPercentile(),
- snapshot.get99thPercentile(),
- snapshot.get999thPercentile());
+ b.setLength(0);
+ b.append("type=HISTOGRAM");
+ append(b, "name", prefix(name));
+ appendCountIfEnabled(b, histogram);
+ appendLongIfEnabled(b, MIN, snapshot::getMin);
+ appendLongIfEnabled(b, MAX, snapshot::getMax);
+ appendDoubleIfEnabled(b, MEAN, snapshot::getMean);
+ appendDoubleIfEnabled(b, STDDEV, snapshot::getStdDev);
+ appendDoubleIfEnabled(b, P50, snapshot::getMedian);
+ appendDoubleIfEnabled(b, P75, snapshot::get75thPercentile);
+ appendDoubleIfEnabled(b, P95, snapshot::get95thPercentile);
+ appendDoubleIfEnabled(b, P98, snapshot::get98thPercentile);
+ appendDoubleIfEnabled(b, P99, snapshot::get99thPercentile);
+ appendDoubleIfEnabled(b, P999, snapshot::get999thPercentile);
+ loggerProxy.log(marker, b.toString());
+ }
+
+ private void logCounter(StringBuilder b, String name, Counter counter) {
+ b.setLength(0);
+ b.append("type=COUNTER");
+ append(b, "name", prefix(name));
+ append(b, COUNT.getCode(), counter.getCount());
+ loggerProxy.log(marker, b.toString());
+ }
+
+ private void logGauge(StringBuilder b, String name, Gauge<?> gauge) {
+ b.setLength(0);
+ b.append("type=GAUGE");
+ append(b, "name", prefix(name));
+ append(b, "value", gauge.getValue());
+ loggerProxy.log(marker, b.toString());
+ }
+
+ private void appendLongDurationIfEnabled(StringBuilder b, MetricAttribute metricAttribute,
+ Supplier<Long> durationSupplier) {
+ if (!getDisabledMetricAttributes().contains(metricAttribute)) {
+ append(b, metricAttribute.getCode(), convertDuration(durationSupplier.get()));
+ }
+ }
+
+ private void appendDoubleDurationIfEnabled(StringBuilder b, MetricAttribute metricAttribute,
+ Supplier<Double> durationSupplier) {
+ if (!getDisabledMetricAttributes().contains(metricAttribute)) {
+ append(b, metricAttribute.getCode(), convertDuration(durationSupplier.get()));
+ }
+ }
+
+ private void appendLongIfEnabled(StringBuilder b, MetricAttribute metricAttribute,
+ Supplier<Long> valueSupplier) {
+ if (!getDisabledMetricAttributes().contains(metricAttribute)) {
+ append(b, metricAttribute.getCode(), valueSupplier.get());
+ }
+ }
+
+ private void appendDoubleIfEnabled(StringBuilder b, MetricAttribute metricAttribute,
+ Supplier<Double> valueSupplier) {
+ if (!getDisabledMetricAttributes().contains(metricAttribute)) {
+ append(b, metricAttribute.getCode(), valueSupplier.get());
+ }
+ }
+
+ private void appendCountIfEnabled(StringBuilder b, Counting counting) {
+ if (!getDisabledMetricAttributes().contains(COUNT)) {
+ append(b, COUNT.getCode(), counting.getCount());
+ }
+ }
+
+ private void appendMetered(StringBuilder b, Metered meter) {
+ appendRateIfEnabled(b, M1_RATE, meter::getOneMinuteRate);
+ appendRateIfEnabled(b, M5_RATE, meter::getFiveMinuteRate);
+ appendRateIfEnabled(b, M15_RATE, meter::getFifteenMinuteRate);
+ appendRateIfEnabled(b, MEAN_RATE, meter::getMeanRate);
+ }
+
+ private void appendRateIfEnabled(StringBuilder b, MetricAttribute metricAttribute, Supplier<Double> rateSupplier) {
+ if (!getDisabledMetricAttributes().contains(metricAttribute)) {
+ append(b, metricAttribute.getCode(), convertRate(rateSupplier.get()));
+ }
+ }
+
+ private void append(StringBuilder b, String key, long value) {
+ b.append(", ").append(key).append('=').append(value);
+ }
+
+ private void append(StringBuilder b, String key, double value) {
+ b.append(", ").append(key).append('=').append(value);
}
- private void logCounter(String name, Counter counter) {
- loggerProxy.log(marker, "type={}, name={}, count={}", "COUNTER", prefix(name), counter.getCount());
+ private void append(StringBuilder b, String key, String value) {
+ b.append(", ").append(key).append('=').append(value);
}
- private void logGauge(String name, Gauge gauge) {
- loggerProxy.log(marker, "type={}, name={}, value={}", "GAUGE", prefix(name), gauge.getValue());
+ private void append(StringBuilder b, String key, Object value) {
+ b.append(", ").append(key).append('=').append(value);
}
@Override
@@ -323,7 +423,7 @@ public class Slf4jReporter extends ScheduledReporter {
this.logger = logger;
}
- abstract void log(Marker marker, String format, Object... arguments);
+ abstract void log(Marker marker, String format);
abstract boolean isEnabled(Marker marker);
}
@@ -335,8 +435,8 @@ public class Slf4jReporter extends ScheduledReporter {
}
@Override
- public void log(Marker marker, String format, Object... arguments) {
- logger.debug(marker, format, arguments);
+ public void log(Marker marker, String format) {
+ logger.debug(marker, format);
}
@Override
@@ -352,8 +452,8 @@ public class Slf4jReporter extends ScheduledReporter {
}
@Override
- public void log(Marker marker, String format, Object... arguments) {
- logger.trace(marker, format, arguments);
+ public void log(Marker marker, String format) {
+ logger.trace(marker, format);
}
@Override
@@ -369,8 +469,8 @@ public class Slf4jReporter extends ScheduledReporter {
}
@Override
- public void log(Marker marker, String format, Object... arguments) {
- logger.info(marker, format, arguments);
+ public void log(Marker marker, String format) {
+ logger.info(marker, format);
}
@Override
@@ -386,8 +486,8 @@ public class Slf4jReporter extends ScheduledReporter {
}
@Override
- public void log(Marker marker, String format, Object... arguments) {
- logger.warn(marker, format, arguments);
+ public void log(Marker marker, String format) {
+ logger.warn(marker, format);
}
@Override
@@ -403,8 +503,8 @@ public class Slf4jReporter extends ScheduledReporter {
}
@Override
- public void log(Marker marker, String format, Object... arguments) {
- logger.error(marker, format, arguments);
+ public void log(Marker marker, String format) {
+ logger.error(marker, format);
}
@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
index cb47a44..9a7da21 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoir.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoir.java
@@ -76,7 +76,7 @@ public class SlidingTimeWindowArrayReservoir implements Reservoir {
}
private long getTick() {
- for (; ; ) {
+ 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
@@ -94,7 +94,8 @@ public class SlidingTimeWindowArrayReservoir implements Reservoir {
if (windowStart < windowEnd) {
measurements.trim(windowStart, windowEnd);
} else {
- measurements.clear(windowEnd, windowStart);
+ // long overflow handling that can happen only after 1 year after class loading
+ measurements.clear();
}
}
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowMovingAverages.java b/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowMovingAverages.java
new file mode 100644
index 0000000..c937ef2
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowMovingAverages.java
@@ -0,0 +1,197 @@
+package com.codahale.metrics;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.LongAdder;
+
+/**
+ * A triple of simple moving average rates (one, five and fifteen minutes rates) as needed by {@link Meter}.
+ * <p>
+ * The averages are unweighted, i.e. they include strictly only the events in the
+ * sliding time window, every event having the same weight. Unlike the
+ * the more widely used {@link ExponentialMovingAverages} implementation,
+ * with this class the moving average rate drops immediately to zero if the last
+ * marked event is older than the time window.
+ * <p>
+ * A {@link Meter} with {@link SlidingTimeWindowMovingAverages} works similarly to
+ * a {@link Histogram} with an {@link SlidingTimeWindowArrayReservoir}, but as a Meter
+ * needs to keep track only of the count of events (not the events itself), the memory
+ * overhead is much smaller. SlidingTimeWindowMovingAverages uses buckets with just one
+ * counter to accumulate the number of events (one bucket per seconds, giving 900 buckets
+ * for the 15 minutes time window).
+ */
+public class SlidingTimeWindowMovingAverages implements MovingAverages {
+
+ private static final long TIME_WINDOW_DURATION_MINUTES = 15;
+ private static final long TICK_INTERVAL = TimeUnit.SECONDS.toNanos(1);
+ private static final Duration TIME_WINDOW_DURATION = Duration.ofMinutes(TIME_WINDOW_DURATION_MINUTES);
+
+ // package private for the benefit of the unit test
+ static final int NUMBER_OF_BUCKETS = (int) (TIME_WINDOW_DURATION.toNanos() / TICK_INTERVAL);
+
+ private final AtomicLong lastTick;
+ private final Clock clock;
+
+ /**
+ * One counter per time bucket/slot (i.e. per second, see TICK_INTERVAL) for the entire
+ * time window (i.e. 15 minutes, see TIME_WINDOW_DURATION_MINUTES)
+ */
+ private ArrayList<LongAdder> buckets;
+
+ /**
+ * Index into buckets, pointing at the bucket containing the oldest counts
+ */
+ private int oldestBucketIndex;
+
+ /**
+ * Index into buckets, pointing at the bucket with the count for the current time (tick)
+ */
+ private int currentBucketIndex;
+
+ /**
+ * Instant at creation time of the time window. Used to calculate the currentBucketIndex
+ * for the instant of a given tick (instant modulo time window duration)
+ */
+ private final Instant bucketBaseTime;
+
+ /**
+ * Instant of the bucket with index oldestBucketIndex
+ */
+ Instant oldestBucketTime;
+
+ /**
+ * Creates a new {@link SlidingTimeWindowMovingAverages}.
+ */
+ public SlidingTimeWindowMovingAverages() {
+ this(Clock.defaultClock());
+ }
+
+ /**
+ * Creates a new {@link SlidingTimeWindowMovingAverages}.
+ *
+ * @param clock the clock to use for the meter ticks
+ */
+ public SlidingTimeWindowMovingAverages(Clock clock) {
+ this.clock = clock;
+ final long startTime = clock.getTick();
+ lastTick = new AtomicLong(startTime);
+
+ buckets = new ArrayList<>(NUMBER_OF_BUCKETS);
+ for (int i = 0; i < NUMBER_OF_BUCKETS; i++) {
+ buckets.add(new LongAdder());
+ }
+ bucketBaseTime = Instant.ofEpochSecond(0L, startTime);
+ oldestBucketTime = bucketBaseTime;
+ oldestBucketIndex = 0;
+ currentBucketIndex = 0;
+ }
+
+ @Override
+ public void update(long n) {
+ buckets.get(currentBucketIndex).add(n);
+ }
+
+ @Override
+ public void tickIfNecessary() {
+ final long oldTick = lastTick.get();
+ final long newTick = clock.getTick();
+ final long age = newTick - oldTick;
+ if (age >= TICK_INTERVAL) {
+ // - the newTick doesn't fall into the same slot as the oldTick anymore
+ // - newLastTick is the lower border time of the new currentBucketIndex slot
+ final long newLastTick = newTick - age % TICK_INTERVAL;
+ if (lastTick.compareAndSet(oldTick, newLastTick)) {
+ Instant currentInstant = Instant.ofEpochSecond(0L, newLastTick);
+ currentBucketIndex = normalizeIndex(calculateIndexOfTick(currentInstant));
+ cleanOldBuckets(currentInstant);
+ }
+ }
+ }
+
+ @Override
+ public double getM15Rate() {
+ return getMinuteRate(15);
+ }
+
+ @Override
+ public double getM5Rate() {
+ return getMinuteRate(5);
+ }
+
+ @Override
+ public double getM1Rate() {
+ return getMinuteRate(1);
+ }
+
+ private double getMinuteRate(int minutes) {
+ Instant now = Instant.ofEpochSecond(0L, lastTick.get());
+ return sumBuckets(now, (int) (TimeUnit.MINUTES.toNanos(minutes) / TICK_INTERVAL));
+ }
+
+ int calculateIndexOfTick(Instant tickTime) {
+ return (int) (Duration.between(bucketBaseTime, tickTime).toNanos() / TICK_INTERVAL);
+ }
+
+ int normalizeIndex(int index) {
+ int mod = index % NUMBER_OF_BUCKETS;
+ return mod >= 0 ? mod : mod + NUMBER_OF_BUCKETS;
+ }
+
+ private void cleanOldBuckets(Instant currentTick) {
+ int newOldestIndex;
+ Instant oldestStillNeededTime = currentTick.minus(TIME_WINDOW_DURATION).plusNanos(TICK_INTERVAL);
+ Instant youngestNotInWindow = oldestBucketTime.plus(TIME_WINDOW_DURATION);
+ if (oldestStillNeededTime.isAfter(youngestNotInWindow)) {
+ // there was no update() call for more than two whole TIME_WINDOW_DURATION
+ newOldestIndex = oldestBucketIndex;
+ oldestBucketTime = currentTick;
+ } else if (oldestStillNeededTime.isAfter(oldestBucketTime)) {
+ newOldestIndex = normalizeIndex(calculateIndexOfTick(oldestStillNeededTime));
+ oldestBucketTime = oldestStillNeededTime;
+ } else {
+ return;
+ }
+
+ cleanBucketRange(oldestBucketIndex, newOldestIndex);
+ oldestBucketIndex = newOldestIndex;
+ }
+
+ private void cleanBucketRange(int fromIndex, int toIndex) {
+ if (fromIndex < toIndex) {
+ for (int i = fromIndex; i < toIndex; i++) {
+ buckets.get(i).reset();
+ }
+ } else {
+ for (int i = fromIndex; i < NUMBER_OF_BUCKETS; i++) {
+ buckets.get(i).reset();
+ }
+ for (int i = 0; i < toIndex; i++) {
+ buckets.get(i).reset();
+ }
+ }
+ }
+
+ private long sumBuckets(Instant toTime, int numberOfBuckets) {
+
+ // increment toIndex to include the current bucket into the sum
+ int toIndex = normalizeIndex(calculateIndexOfTick(toTime) + 1);
+ int fromIndex = normalizeIndex(toIndex - numberOfBuckets);
+ LongAdder adder = new LongAdder();
+
+ if (fromIndex < toIndex) {
+ buckets.stream()
+ .skip(fromIndex)
+ .limit(toIndex - fromIndex)
+ .mapToLong(LongAdder::longValue)
+ .forEach(adder::add);
+ } else {
+ buckets.stream().limit(toIndex).mapToLong(LongAdder::longValue).forEach(adder::add);
+ buckets.stream().skip(fromIndex).mapToLong(LongAdder::longValue).forEach(adder::add);
+ }
+ long retval = adder.longValue();
+ return retval;
+ }
+}
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 e1a9d09..7cbb90a 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowReservoir.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowReservoir.java
@@ -21,6 +21,7 @@ public class SlidingTimeWindowReservoir implements Reservoir {
private final long window;
private final AtomicLong lastTick;
private final AtomicLong count;
+ private final long startTick;
/**
* Creates a new {@link SlidingTimeWindowReservoir} with the given window of time.
@@ -40,10 +41,11 @@ public class SlidingTimeWindowReservoir implements Reservoir {
* @param clock the {@link Clock} to use
*/
public SlidingTimeWindowReservoir(long window, TimeUnit windowUnit, Clock clock) {
+ this.startTick = clock.getTick();
this.clock = clock;
- this.measurements = new ConcurrentSkipListMap<Long, Long>();
+ this.measurements = new ConcurrentSkipListMap<>();
this.window = windowUnit.toNanos(window) * COLLISION_BUFFER;
- this.lastTick = new AtomicLong(clock.getTick() * COLLISION_BUFFER);
+ this.lastTick = new AtomicLong((clock.getTick() - startTick) * COLLISION_BUFFER);
this.count = new AtomicLong();
}
@@ -68,9 +70,9 @@ public class SlidingTimeWindowReservoir implements Reservoir {
}
private long getTick() {
- for (; ; ) {
+ for ( ;; ) {
final long oldTick = lastTick.get();
- final long tick = clock.getTick() * COLLISION_BUFFER;
+ 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 > 0 ? tick : oldTick + 1;
if (lastTick.compareAndSet(oldTick, newTick)) {
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Snapshot.java b/metrics-core/src/main/java/com/codahale/metrics/Snapshot.java
index a04804b..aca448d 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Snapshot.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Snapshot.java
@@ -10,7 +10,7 @@ public abstract class Snapshot {
/**
* Returns the value at the given quantile.
*
- * @param quantile a given quantile, in {@code [0..1]}
+ * @param quantile a given quantile, in {@code [0..1]}
* @return the value in the distribution at {@code quantile}
*/
public abstract double getValue(double quantile);
@@ -28,7 +28,7 @@ public abstract class Snapshot {
* @return the number of values
*/
public abstract int size();
-
+
/**
* Returns the median value in the distribution.
*
@@ -117,5 +117,5 @@ public abstract class Snapshot {
* @param output an output stream
*/
public abstract void dump(OutputStream output);
-
+
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Striped64.java b/metrics-core/src/main/java/com/codahale/metrics/Striped64.java
deleted file mode 100644
index 3652d95..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/Striped64.java
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Written by Doug Lea with assistance from members of JCP JSR-166
- * Expert Group and released to the public domain, as explained at
- * http://creativecommons.org/publicdomain/zero/1.0/
- *
- * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.8&view=markup
- */
-
-package com.codahale.metrics;
-
-import java.util.Random;
-import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
-import java.util.concurrent.atomic.AtomicLongFieldUpdater;
-
-// CHECKSTYLE:OFF
-/**
- * A package-local class holding common representation and mechanics for classes supporting dynamic
- * striping on 64bit values. The class extends Number so that concrete subclasses must publicly do
- * so.
- */
-@SuppressWarnings("all")
-abstract class Striped64 extends Number {
- /*
- * This class maintains a lazily-initialized table of atomically
- * updated variables, plus an extra "base" field. The table size
- * is a power of two. Indexing uses masked per-thread hash codes.
- * Nearly all declarations in this class are package-private,
- * accessed directly by subclasses.
- *
- * Table entries are of class Cell; a variant of AtomicLong padded
- * to reduce cache contention on most processors. Padding is
- * overkill for most Atomics because they are usually irregularly
- * scattered in memory and thus don't interfere much with each
- * other. But Atomic objects residing in arrays will tend to be
- * placed adjacent to each other, and so will most often share
- * cache lines (with a huge negative performance impact) without
- * this precaution.
- *
- * In part because Cells are relatively large, we avoid creating
- * them until they are needed. When there is no contention, all
- * updates are made to the base field. Upon first contention (a
- * failed CAS on base update), the table is initialized to size 2.
- * The table size is doubled upon further contention until
- * reaching the nearest power of two greater than or equal to the
- * number of CPUS. Table slots remain empty (null) until they are
- * needed.
- *
- * A single spinlock ("busy") is used for initializing and
- * resizing the table, as well as populating slots with new Cells.
- * There is no need for a blocking lock; when the lock is not
- * available, threads try other slots (or the base). During these
- * retries, there is increased contention and reduced locality,
- * which is still better than alternatives.
- *
- * Per-thread hash codes are initialized to random values.
- * Contention and/or table collisions are indicated by failed
- * CASes when performing an update operation (see method
- * retryUpdate). Upon a collision, if the table size is less than
- * the capacity, it is doubled in size unless some other thread
- * holds the lock. If a hashed slot is empty, and lock is
- * available, a new Cell is created. Otherwise, if the slot
- * exists, a CAS is tried. Retries proceed by "double hashing",
- * using a secondary hash (Marsaglia XorShift) to try to find a
- * free slot.
- *
- * The table size is capped because, when there are more threads
- * than CPUs, supposing that each thread were bound to a CPU,
- * there would exist a perfect hash function mapping threads to
- * slots that eliminates collisions. When we reach capacity, we
- * search for this mapping by randomly varying the hash codes of
- * colliding threads. Because search is random, and collisions
- * only become known via CAS failures, convergence can be slow,
- * and because threads are typically not bound to CPUS forever,
- * may not occur at all. However, despite these limitations,
- * observed contention rates are typically low in these cases.
- *
- * It is possible for a Cell to become unused when threads that
- * once hashed to it terminate, as well as in the case where
- * doubling the table causes no thread to hash to it under
- * expanded mask. We do not try to detect or remove such cells,
- * under the assumption that for long-running instances, observed
- * contention levels will recur, so the cells will eventually be
- * needed again; and for short-lived ones, it does not matter.
- */
-
- /**
- * Padded variant of AtomicLong supporting only raw accesses plus CAS. The value field is placed
- * between pads, hoping that the JVM doesn't reorder them.
- * <p/>
- * JVM intrinsics note: It would be possible to use a release-only form of CAS here, if it were
- * provided.
- */
- static final class Cell {
- volatile long p0, p1, p2, p3, p4, p5, p6;
- volatile long value;
- volatile long q0, q1, q2, q3, q4, q5, q6;
-
- Cell(long x) {
- value = x;
- }
-
- final boolean cas(long cmp, long val) {
- return valueUpdater.compareAndSet(this, cmp, val);
- }
-
- private static final AtomicLongFieldUpdater<Cell> valueUpdater = AtomicLongFieldUpdater.newUpdater(Cell.class, "value");
-
- }
-
- /**
- * Holder for the thread-local hash code. The code is initially random, but may be set to a
- * different value upon collisions.
- */
- static final class HashCode {
- static final Random rng = new Random();
- int code;
-
- HashCode() {
- int h = rng.nextInt(); // Avoid zero to allow xorShift rehash
- code = (h == 0) ? 1 : h;
- }
- }
-
- /**
- * The corresponding ThreadLocal class
- */
- static final class ThreadHashCode extends ThreadLocal<HashCode> {
- public HashCode initialValue() {
- return new HashCode();
- }
- }
-
- 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.
- */
- static final ThreadHashCode threadHashCode = new ThreadHashCode();
-
- /**
- * Number of CPUS, to place bound on table size
- */
- static final int NCPU = Runtime.getRuntime().availableProcessors();
-
- /**
- * Table of cells. When non-null, size is a power of 2.
- */
- transient volatile Cell[] cells;
-
- /**
- * Base value, used mainly when there is no contention, but also as a fallback during table
- * initialization races. Updated via CAS.
- */
- transient volatile long base;
-
- /**
- * Spinlock (locked via CAS) used when resizing and/or creating Cells.
- */
- transient volatile int busy;
-
- /**
- * Package-private default constructor
- */
- Striped64() {
- }
-
- /**
- * CASes the base field.
- */
- final boolean casBase(long cmp, long val) {
- return baseUpdater.compareAndSet(this, cmp, val);
- }
-
- /**
- * CASes the busy field from 0 to 1 to acquire lock.
- */
- final boolean casBusy() {
- return busyUpdater.compareAndSet(this, 0, 1);
- }
-
- /**
- * Computes the function of current and new value. Subclasses should open-code this update
- * function for most uses, but the virtualized form is needed within retryUpdate.
- *
- * @param currentValue the current value (of either base or a cell)
- * @param newValue the argument from a user update call
- * @return result of the update function
- */
- abstract long fn(long currentValue, long newValue);
-
- /**
- * Handles cases of updates involving initialization, resizing, creating new Cells, and/or
- * contention. See above for explanation. This method suffers the usual non-modularity problems
- * of optimistic retry code, relying on rechecked sets of reads.
- *
- * @param x the value
- * @param hc the hash code holder
- * @param wasUncontended false if CAS failed before call
- */
- final void retryUpdate(long x, HashCode hc, boolean wasUncontended) {
- int h = hc.code;
- boolean collide = false; // True if last slot nonempty
- for (; ; ) {
- Cell[] as;
- Cell a;
- int n;
- long v;
- if ((as = cells) != null && (n = as.length) > 0) {
- if ((a = as[(n - 1) & h]) == null) {
- if (busy == 0) { // Try to attach new Cell
- Cell r = new Cell(x); // Optimistically create
- if (busy == 0 && casBusy()) {
- boolean created = false;
- try { // Recheck under lock
- Cell[] rs;
- int m, j;
- if ((rs = cells) != null &&
- (m = rs.length) > 0 &&
- rs[j = (m - 1) & h] == null) {
- rs[j] = r;
- created = true;
- }
- } finally {
- busy = 0;
- }
- if (created)
- break;
- continue; // Slot is now non-empty
- }
- }
- collide = false;
- } else if (!wasUncontended) // CAS already known to fail
- wasUncontended = true; // Continue after rehash
- else if (a.cas(v = a.value, fn(v, x)))
- break;
- else if (n >= NCPU || cells != as)
- collide = false; // At max size or stale
- else if (!collide)
- collide = true;
- else if (busy == 0 && casBusy()) {
- try {
- if (cells == as) { // Expand table unless stale
- Cell[] rs = new Cell[n << 1];
- for (int i = 0; i < n; ++i)
- rs[i] = as[i];
- cells = rs;
- }
- } finally {
- busy = 0;
- }
- collide = false;
- continue; // Retry with expanded table
- }
- h ^= h << 13; // Rehash
- h ^= h >>> 17;
- h ^= h << 5;
- } else if (busy == 0 && cells == as && casBusy()) {
- boolean init = false;
- try { // Initialize table
- if (cells == as) {
- Cell[] rs = new Cell[2];
- rs[h & 1] = new Cell(x);
- cells = rs;
- init = true;
- }
- } finally {
- busy = 0;
- }
- if (init)
- break;
- } else if (casBase(v = base, fn(v, x)))
- break; // Fall back on using base
- }
- hc.code = h; // Record index for next time
- }
-
-
- /**
- * Sets base and all cells to the given value.
- */
- final void internalReset(long initialValue) {
- Cell[] as = cells;
- base = initialValue;
- if (as != null) {
- int n = as.length;
- for (int i = 0; i < n; ++i) {
- Cell a = as[i];
- if (a != null)
- a.value = initialValue;
- }
- }
- }
-
-}
-// CHECKSTYLE:ON
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ThreadLocalRandom.java b/metrics-core/src/main/java/com/codahale/metrics/ThreadLocalRandom.java
deleted file mode 100644
index 14dd264..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/ThreadLocalRandom.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Written by Doug Lea with assistance from members of JCP JSR-166
- * Expert Group and released to the public domain, as explained at
- * http://creativecommons.org/publicdomain/zero/1.0/
- *
- * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/ThreadLocalRandom.java?view=markup
- */
-
-package com.codahale.metrics;
-
-import java.util.Random;
-
-// CHECKSTYLE:OFF
-/**
- * Copied directly from the JSR-166 project.
- */
-@SuppressWarnings("all")
-class ThreadLocalRandom extends Random {
- // same constants as Random, but must be redeclared because private
- private static final long multiplier = 0x5DEECE66DL;
- private static final long addend = 0xBL;
- private static final long mask = (1L << 48) - 1;
-
- /**
- * The random seed. We can't use super.seed.
- */
- private long rnd;
-
- /**
- * Initialization flag to permit calls to setSeed to succeed only while executing the Random
- * constructor. We can't allow others since it would cause setting seed in one part of a
- * program to unintentionally impact other usages by the thread.
- */
- boolean initialized;
-
- // Padding to help avoid memory contention among seed updates in
- // different TLRs in the common case that they are located near
- // each other.
- private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
-
- /**
- * The actual ThreadLocal
- */
- private static final ThreadLocal<ThreadLocalRandom> localRandom =
- new ThreadLocal<ThreadLocalRandom>() {
- protected ThreadLocalRandom initialValue() {
- return new ThreadLocalRandom();
- }
- };
-
-
- /**
- * Constructor called only by localRandom.initialValue.
- */
- ThreadLocalRandom() {
- super();
- initialized = true;
- }
-
- /**
- * Returns the current thread's {@code ThreadLocalRandom}.
- *
- * @return the current thread's {@code ThreadLocalRandom}
- */
- public static ThreadLocalRandom current() {
- return localRandom.get();
- }
-
- /**
- * Throws {@code UnsupportedOperationException}. Setting seeds in this generator is not
- * supported.
- *
- * @throws UnsupportedOperationException always
- */
- public void setSeed(long seed) {
- if (initialized)
- throw new UnsupportedOperationException();
- rnd = (seed ^ multiplier) & mask;
- }
-
- protected int next(int bits) {
- rnd = (rnd * multiplier + addend) & mask;
- return (int) (rnd >>> (48 - bits));
- }
-
- /**
- * Returns a pseudorandom, uniformly distributed value between the given least value (inclusive)
- * and bound (exclusive).
- *
- * @param least the least value returned
- * @param bound the upper bound (exclusive)
- * @return the next value
- * @throws IllegalArgumentException if least greater than or equal to bound
- */
- public int nextInt(int least, int bound) {
- if (least >= bound)
- throw new IllegalArgumentException();
- return nextInt(bound - least) + least;
- }
-
- /**
- * Returns a pseudorandom, uniformly distributed value between 0 (inclusive) and the specified
- * value (exclusive).
- *
- * @param n the bound on the random number to be returned. Must be positive.
- * @return the next value
- * @throws IllegalArgumentException if n is not positive
- */
- public long nextLong(long n) {
- if (n <= 0)
- throw new IllegalArgumentException("n must be positive");
- // Divide n by two until small enough for nextInt. On each
- // iteration (at most 31 of them but usually much less),
- // randomly choose both whether to include high bit in result
- // (offset) and whether to continue with the lower vs upper
- // half (which makes a difference only if odd).
- long offset = 0;
- while (n >= Integer.MAX_VALUE) {
- final int bits = next(2);
- final long half = n >>> 1;
- final long nextn = ((bits & 2) == 0) ? half : n - half;
- if ((bits & 1) == 0)
- offset += n - nextn;
- n = nextn;
- }
- return offset + nextInt((int) n);
- }
-
- /**
- * Returns a pseudorandom, uniformly distributed value between the given least value (inclusive)
- * and bound (exclusive).
- *
- * @param least the least value returned
- * @param bound the upper bound (exclusive)
- * @return the next value
- * @throws IllegalArgumentException if least greater than or equal to bound
- */
- public long nextLong(long least, long bound) {
- if (least >= bound)
- throw new IllegalArgumentException();
- return nextLong(bound - least) + least;
- }
-
- /**
- * Returns a pseudorandom, uniformly distributed {@code double} value between 0 (inclusive) and
- * the specified value (exclusive).
- *
- * @param n the bound on the random number to be returned. Must be positive.
- * @return the next value
- * @throws IllegalArgumentException if n is not positive
- */
- public double nextDouble(double n) {
- if (n <= 0)
- throw new IllegalArgumentException("n must be positive");
- return nextDouble() * n;
- }
-
- /**
- * Returns a pseudorandom, uniformly distributed value between the given least value (inclusive)
- * and bound (exclusive).
- *
- * @param least the least value returned
- * @param bound the upper bound (exclusive)
- * @return the next value
- * @throws IllegalArgumentException if least greater than or equal to bound
- */
- public double nextDouble(double least, double bound) {
- if (least >= bound)
- throw new IllegalArgumentException();
- return nextDouble() * (bound - least) + least;
- }
-
- private static final long serialVersionUID = -5851777807851030925L;
-}
-// 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
deleted file mode 100644
index 08dc9f0..0000000
--- a/metrics-core/src/main/java/com/codahale/metrics/ThreadLocalRandomProxy.java
+++ /dev/null
@@ -1,50 +0,0 @@
-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 e49841a..8070123 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Timer.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Timer.java
@@ -1,8 +1,9 @@
package com.codahale.metrics;
-import java.io.Closeable;
+import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
/**
* A timer metric which aggregates timing durations and provides duration statistics, plus
@@ -14,12 +15,12 @@ public class Timer implements Metered, Sampling {
*
* @see Timer#time()
*/
- public static class Context implements Closeable {
+ public static class Context implements AutoCloseable {
private final Timer timer;
private final Clock clock;
private final long startTime;
- private Context(Timer timer, Clock clock) {
+ Context(Timer timer, Clock clock) {
this.timer = timer;
this.clock = clock;
this.startTime = clock.getTick();
@@ -28,6 +29,7 @@ public class Timer implements Metered, Sampling {
/**
* Updates the timer with the difference between current and start time. Call to this method will
* not reset the start time. Multiple calls result in multiple updates.
+ *
* @return the elapsed time in nanoseconds
*/
public long stop() {
@@ -36,7 +38,9 @@ public class Timer implements Metered, Sampling {
return elapsed;
}
- /** Equivalent to calling {@link #stop()}. */
+ /**
+ * Equivalent to calling {@link #stop()}.
+ */
@Override
public void close() {
stop();
@@ -68,12 +72,16 @@ public class Timer implements Metered, Sampling {
* Creates a new {@link Timer} that uses the given {@link Reservoir} and {@link Clock}.
*
* @param reservoir the {@link Reservoir} implementation the timer should use
- * @param clock the {@link Clock} implementation the timer should use
+ * @param clock the {@link Clock} implementation the timer should use
*/
public Timer(Reservoir reservoir, Clock clock) {
- this.meter = new Meter(clock);
+ this(new Meter(clock), new Histogram(reservoir), clock);
+ }
+
+ public Timer(Meter meter, Histogram histogram, Clock clock) {
+ this.meter = meter;
+ this.histogram = histogram;
this.clock = clock;
- this.histogram = new Histogram(reservoir);
}
/**
@@ -86,6 +94,15 @@ public class Timer implements Metered, Sampling {
update(unit.toNanos(duration));
}
+ /**
+ * Adds a recorded duration.
+ *
+ * @param duration the {@link Duration} to add to the timer. Negative or zero value are ignored.
+ */
+ public void update(Duration duration) {
+ update(duration.toNanos());
+ }
+
/**
* Times and records the duration of event.
*
@@ -104,6 +121,24 @@ public class Timer implements Metered, Sampling {
}
}
+ /**
+ * Times and records the duration of event. Should not throw exceptions, for that use the
+ * {@link #time(Callable)} method.
+ *
+ * @param event a {@link Supplier} whose {@link Supplier#get()} method implements a process
+ * whose duration should be timed
+ * @param <T> the type of the value returned by {@code event}
+ * @return the value returned by {@code event}
+ */
+ public <T> T timeSupplier(Supplier<T> event) {
+ final long startTime = clock.getTick();
+ try {
+ return event.get();
+ } finally {
+ update(clock.getTick() - startTime);
+ }
+ }
+
/**
* Times and records the duration of event.
*
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 8b461fa..a2c2983 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/UniformReservoir.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/UniformReservoir.java
@@ -1,7 +1,6 @@
package com.codahale.metrics;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;
@@ -13,7 +12,6 @@ import java.util.concurrent.atomic.AtomicLongArray;
*/
public class UniformReservoir implements Reservoir {
private static final int DEFAULT_SIZE = 1028;
- private static final int BITS_PER_LONG = 63;
private final AtomicLong count = new AtomicLong();
private final AtomicLongArray values;
@@ -53,35 +51,19 @@ public class UniformReservoir implements Reservoir {
if (c <= values.length()) {
values.set((int) c - 1, value);
} else {
- final long r = nextLong(c);
+ final long r = ThreadLocalRandom.current().nextLong(c);
if (r < values.length()) {
values.set((int) r, value);
}
}
}
- /**
- * Get a pseudo-random long uniformly between 0 and n-1. Stolen from
- * {@link java.util.Random#nextInt()}.
- *
- * @param n the bound
- * @return a value select randomly from the range {@code [0..n)}.
- */
- private static long nextLong(long n) {
- long bits, val;
- do {
- bits = ThreadLocalRandomProxy.current().nextLong() & (~(1L << BITS_PER_LONG));
- val = bits % n;
- } while (bits - val + (n - 1) < 0L);
- return val;
- }
-
@Override
public Snapshot getSnapshot() {
final int s = size();
long[] copy = new long[s];
for (int i = 0; i < s; i++) {
- copy[i] = 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 16c5e1e..de32b60 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/UniformSnapshot.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/UniformSnapshot.java
@@ -3,24 +3,23 @@ package com.codahale.metrics;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
-import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import static java.lang.Math.floor;
+import static java.nio.charset.StandardCharsets.UTF_8;
/**
* A statistical snapshot of a {@link UniformSnapshot}.
*/
public class UniformSnapshot extends Snapshot {
- private static final Charset UTF_8 = Charset.forName("UTF-8");
private final long[] values;
/**
* 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
*/
public UniformSnapshot(Collection<Long> values) {
final Object[] copy = values.toArray();
@@ -34,7 +33,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 that can be used by this class directly
+ * @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);
@@ -44,12 +43,12 @@ public class UniformSnapshot extends Snapshot {
/**
* Returns the value at the given quantile.
*
- * @param quantile a given quantile, in {@code [0..1]}
+ * @param quantile a given quantile, in {@code [0..1]}
* @return the value in the distribution at {@code quantile}
*/
@Override
public double getValue(double quantile) {
- if (quantile < 0.0 || quantile > 1.0 || Double.isNaN( quantile )) {
+ if (quantile < 0.0 || quantile > 1.0 || Double.isNaN(quantile)) {
throw new IllegalArgumentException(quantile + " is not in [0..1]");
}
@@ -169,13 +168,10 @@ public class UniformSnapshot extends Snapshot {
*/
@Override
public void dump(OutputStream output) {
- final PrintWriter out = new PrintWriter(new OutputStreamWriter(output, UTF_8));
- try {
+ try (PrintWriter out = new PrintWriter(new OutputStreamWriter(output, UTF_8))) {
for (long value : values) {
out.printf("%d%n", value);
}
- } finally {
- out.close();
}
}
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/WeightedSnapshot.java b/metrics-core/src/main/java/com/codahale/metrics/WeightedSnapshot.java
index 96200fc..e0a0046 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/WeightedSnapshot.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/WeightedSnapshot.java
@@ -3,16 +3,17 @@ package com.codahale.metrics;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
-import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
/**
* A statistical snapshot of a {@link WeightedSnapshot}.
*/
public class WeightedSnapshot extends Snapshot {
-
+
/**
* A single sample item with value and its weights for {@link WeightedSnapshot}.
*/
@@ -25,8 +26,6 @@ public class WeightedSnapshot extends Snapshot {
this.weight = weight;
}
}
-
- private static final Charset UTF_8 = Charset.forName("UTF-8");
private final long[] values;
private final double[] normWeights;
@@ -35,27 +34,17 @@ public class WeightedSnapshot 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
*/
public WeightedSnapshot(Collection<WeightedSample> values) {
- final WeightedSample[] copy = values.toArray( new WeightedSample[]{} );
-
- Arrays.sort(copy, new Comparator<WeightedSample>() {
- @Override
- public int compare(WeightedSample o1, WeightedSample o2) {
- if (o1.value > o2.value)
- return 1;
- if (o1.value < o2.value)
- return -1;
- return 0;
- }
- }
- );
+ final WeightedSample[] copy = values.toArray(new WeightedSample[]{});
+
+ Arrays.sort(copy, Comparator.comparingLong(w -> w.value));
this.values = new long[copy.length];
this.normWeights = new double[copy.length];
this.quantiles = new double[copy.length];
-
+
double sumWeight = 0;
for (WeightedSample sample : copy) {
sumWeight += sample.weight;
@@ -74,12 +63,12 @@ public class WeightedSnapshot extends Snapshot {
/**
* Returns the value at the given quantile.
*
- * @param quantile a given quantile, in {@code [0..1]}
+ * @param quantile a given quantile, in {@code [0..1]}
* @return the value in the distribution at {@code quantile}
*/
@Override
public double getValue(double quantile) {
- if (quantile < 0.0 || quantile > 1.0 || Double.isNaN( quantile )) {
+ if (quantile < 0.0 || quantile > 1.0 || Double.isNaN(quantile)) {
throw new IllegalArgumentException(quantile + " is not in [0..1]");
}
@@ -99,7 +88,7 @@ public class WeightedSnapshot extends Snapshot {
return values[values.length - 1];
}
- return values[(int) posx];
+ return values[posx];
}
/**
@@ -184,7 +173,7 @@ public class WeightedSnapshot extends Snapshot {
for (int i = 0; i < values.length; i++) {
final double diff = values[i] - mean;
- variance += normWeights[i] * diff*diff;
+ variance += normWeights[i] * diff * diff;
}
return Math.sqrt(variance);
@@ -197,13 +186,10 @@ public class WeightedSnapshot extends Snapshot {
*/
@Override
public void dump(OutputStream output) {
- final PrintWriter out = new PrintWriter(new OutputStreamWriter(output, UTF_8));
- try {
+ try (PrintWriter out = new PrintWriter(new OutputStreamWriter(output, UTF_8))) {
for (long value : values) {
out.printf("%d%n", value);
}
- } finally {
- out.close();
}
}
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/CachedGaugeTest.java b/metrics-core/src/test/java/com/codahale/metrics/CachedGaugeTest.java
index 7e64cdb..38c02f2 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/CachedGaugeTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/CachedGaugeTest.java
@@ -1,13 +1,26 @@
package com.codahale.metrics;
import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
public class CachedGaugeTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CachedGaugeTest.class);
+ private static final int THREAD_COUNT = 10;
+ private static final long RUNNING_TIME_MILLIS = TimeUnit.SECONDS.toMillis(10);
+
private final AtomicInteger value = new AtomicInteger(0);
private final Gauge<Integer> gauge = new CachedGauge<Integer>(100, TimeUnit.MILLISECONDS) {
@Override
@@ -15,9 +28,21 @@ public class CachedGaugeTest {
return value.incrementAndGet();
}
};
+ private final Gauge<Integer> shortTimeoutGauge = new CachedGauge<Integer>(1, TimeUnit.MILLISECONDS) {
+ @Override
+ protected Integer loadValue() {
+ try {
+ Thread.sleep(5);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Thread was interrupted", e);
+ }
+ return value.incrementAndGet();
+ }
+ };
+ private final ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
@Test
- public void cachesTheValueForTheGivenPeriod() throws Exception {
+ public void cachesTheValueForTheGivenPeriod() {
assertThat(gauge.getValue())
.isEqualTo(1);
assertThat(gauge.getValue())
@@ -37,4 +62,70 @@ public class CachedGaugeTest {
assertThat(gauge.getValue())
.isEqualTo(2);
}
+
+ @Test
+ public void reloadsCachedValueInNegativeTime() throws Exception {
+ AtomicLong time = new AtomicLong(-2L);
+ Clock clock = new Clock() {
+ @Override
+ public long getTick() {
+ return time.get();
+ }
+ };
+ Gauge<Integer> clockGauge = new CachedGauge<Integer>(clock, 1, TimeUnit.NANOSECONDS) {
+ @Override
+ protected Integer loadValue() {
+ return value.incrementAndGet();
+ }
+ };
+ assertThat(clockGauge.getValue())
+ .isEqualTo(1);
+ assertThat(clockGauge.getValue())
+ .isEqualTo(1);
+
+ time.set(-1L);
+
+ assertThat(clockGauge.getValue())
+ .isEqualTo(2);
+ assertThat(clockGauge.getValue())
+ .isEqualTo(2);
+ }
+
+ @Test
+ public void multipleThreadAccessReturnsConsistentResults() throws Exception {
+ List<Future<Boolean>> futures = new ArrayList<>(THREAD_COUNT);
+
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ Future<Boolean> future = executor.submit(() -> {
+ long startTime = System.currentTimeMillis();
+ int lastValue = 0;
+
+ do {
+ Integer newValue = shortTimeoutGauge.getValue();
+
+ if (newValue == null) {
+ LOGGER.warn("Cached gauge returned null value");
+ return false;
+ }
+
+ if (newValue < lastValue) {
+ LOGGER.error("Cached gauge returned stale value, last: {}, new: {}", lastValue, newValue);
+ return false;
+ }
+
+ lastValue = newValue;
+ } while (System.currentTimeMillis() - startTime <= RUNNING_TIME_MILLIS);
+
+ return true;
+ });
+
+ futures.add(future);
+ }
+
+ for (int i = 0; i < futures.size(); i++) {
+ assertTrue("Future " + i + " failed", futures.get(i).get());
+ }
+
+ executor.shutdown();
+ }
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/ChunkedAssociativeLongArrayTest.java b/metrics-core/src/test/java/com/codahale/metrics/ChunkedAssociativeLongArrayTest.java
index b83a7e1..62dda3e 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ChunkedAssociativeLongArrayTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ChunkedAssociativeLongArrayTest.java
@@ -6,37 +6,6 @@ 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);
@@ -52,20 +21,20 @@ public class ChunkedAssociativeLongArrayTest {
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) ]");
+ .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});
+ .isEqualTo(new long[]{3, 1, 5, 0, 8, 0, 5, 5, -9, 5});
then(array.size())
- .isEqualTo(10);
+ .isEqualTo(10);
array.trim(-2, 20);
then(array.out())
- .isEqualTo("[(-2: 1) (0: 5) ]->[(3: 0) (9: 8) (15: 0) ]->[(19: 5) ]");
+ .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});
+ .isEqualTo(new long[]{1, 5, 0, 8, 0, 5});
then(array.size())
- .isEqualTo(6);
+ .isEqualTo(6);
}
-}
\ No newline at end of file
+}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/ClassMetadataTest.java b/metrics-core/src/test/java/com/codahale/metrics/ClassMetadataTest.java
new file mode 100644
index 0000000..ab95351
--- /dev/null
+++ b/metrics-core/src/test/java/com/codahale/metrics/ClassMetadataTest.java
@@ -0,0 +1,13 @@
+package com.codahale.metrics;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ClassMetadataTest {
+ @Test
+ public void testParameterMetadataIsAvailable() throws NoSuchMethodException {
+ assertThat(DefaultSettableGauge.class.getConstructor(Object.class).getParameters())
+ .allSatisfy(parameter -> assertThat(parameter.isNamePresent()).isTrue());
+ }
+}
\ No newline at end of file
diff --git a/metrics-core/src/test/java/com/codahale/metrics/ClockTest.java b/metrics-core/src/test/java/com/codahale/metrics/ClockTest.java
index 3b661f4..79d6b81 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ClockTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ClockTest.java
@@ -2,40 +2,26 @@ package com.codahale.metrics;
import org.junit.Test;
-import java.lang.management.ManagementFactory;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.offset;
public class ClockTest {
- @Test
- public void cpuTimeClock() throws Exception {
- final Clock.CpuTimeClock clock = new Clock.CpuTimeClock();
-
- assertThat((double) clock.getTime())
- .isEqualTo(System.currentTimeMillis(),
- offset(100.0));
-
- assertThat((double) clock.getTick())
- .isEqualTo(ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime(),
- offset(1000000.0));
- }
@Test
- public void userTimeClock() throws Exception {
+ public void userTimeClock() {
final Clock.UserTimeClock clock = new Clock.UserTimeClock();
assertThat((double) clock.getTime())
.isEqualTo(System.currentTimeMillis(),
- offset(100.0));
+ offset(100.0));
assertThat((double) clock.getTick())
.isEqualTo(System.nanoTime(),
- offset(100000.0));
+ offset(1000000.0));
}
@Test
- public void defaultsToUserTime() throws Exception {
+ public void defaultsToUserTime() {
assertThat(Clock.defaultClock())
.isInstanceOf(Clock.UserTimeClock.class);
}
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 caaa00b..43eb6ed 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ConsoleReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ConsoleReporterTest.java
@@ -1,17 +1,19 @@
package com.codahale.metrics;
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
+import java.util.EnumSet;
import java.util.Locale;
-import java.util.TimeZone;
+import java.util.Set;
import java.util.SortedMap;
+import java.util.TimeZone;
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;
@@ -20,41 +22,49 @@ import static org.mockito.Mockito.when;
public class ConsoleReporterTest {
private final Locale locale = Locale.US;
- private final TimeZone timeZone = TimeZone.getTimeZone("PST");
+ private final TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
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)
- .withClock(clock)
- .formattedFor(timeZone)
- .convertRatesTo(TimeUnit.SECONDS)
- .convertDurationsTo(TimeUnit.MILLISECONDS)
- .filter(MetricFilter.ALL)
- .build();
+ .outputTo(output)
+ .formattedFor(locale)
+ .withClock(clock)
+ .formattedFor(timeZone)
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .filter(MetricFilter.ALL)
+ .build();
+ private String dateHeader;
@Before
public void setUp() throws Exception {
when(clock.getTime()).thenReturn(1363568676000L);
+ // JDK9 has changed the java.text.DateFormat API implementation according to Unicode.
+ // See http://mail.openjdk.java.net/pipermail/jdk9-dev/2017-April/005732.html
+ dateHeader = SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_1_8) ?
+ "3/17/13 6:04:36 PM =============================================================" :
+ // https://bugs.openjdk.org/browse/JDK-8304925
+ SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_20) ?
+ "3/17/13, 6:04:36\u202FPM ============================================================" :
+ "3/17/13, 6:04:36 PM ============================================================";
}
@Test
public void reportsGaugeValues() throws Exception {
- final Gauge gauge = mock(Gauge.class);
- when(gauge.getValue()).thenReturn(1);
+ final Gauge<Integer> gauge = () -> 1;
reporter.report(map("gauge", gauge),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
assertThat(consoleOutput())
.isEqualTo(lines(
- "3/17/13 6:04:36 PM =============================================================",
+ dateHeader,
"",
"-- Gauges ----------------------------------------------------------------------",
"gauge",
@@ -69,15 +79,15 @@ public class ConsoleReporterTest {
final Counter counter = mock(Counter.class);
when(counter.getCount()).thenReturn(100L);
- reporter.report(this.<Gauge>map(),
- map("test.counter", counter),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ reporter.report(map(),
+ map("test.counter", counter),
+ map(),
+ map(),
+ map());
assertThat(consoleOutput())
.isEqualTo(lines(
- "3/17/13 6:04:36 PM =============================================================",
+ dateHeader,
"",
"-- Counters --------------------------------------------------------------------",
"test.counter",
@@ -106,15 +116,15 @@ public class ConsoleReporterTest {
when(histogram.getSnapshot()).thenReturn(snapshot);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- map("test.histogram", histogram),
- this.<Meter>map(),
- this.<Timer>map());
+ reporter.report(map(),
+ map(),
+ map("test.histogram", histogram),
+ map(),
+ map());
assertThat(consoleOutput())
.isEqualTo(lines(
- "3/17/13 6:04:36 PM =============================================================",
+ dateHeader,
"",
"-- Histograms ------------------------------------------------------------------",
"test.histogram",
@@ -143,15 +153,15 @@ public class ConsoleReporterTest {
when(meter.getFiveMinuteRate()).thenReturn(4.0);
when(meter.getFifteenMinuteRate()).thenReturn(5.0);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- map("test.meter", meter),
- this.<Timer>map());
+ reporter.report(map(),
+ map(),
+ map(),
+ map("test.meter", meter),
+ map());
assertThat(consoleOutput())
.isEqualTo(lines(
- "3/17/13 6:04:36 PM =============================================================",
+ dateHeader,
"",
"-- Meters ----------------------------------------------------------------------",
"test.meter",
@@ -185,19 +195,19 @@ public class ConsoleReporterTest {
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));
+ .toNanos(1000));
when(timer.getSnapshot()).thenReturn(snapshot);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- map("test.another.timer", timer));
+ reporter.report(map(),
+ map(),
+ map(),
+ map(),
+ map("test.another.timer", timer));
assertThat(consoleOutput())
.isEqualTo(lines(
- "3/17/13 6:04:36 PM =============================================================",
+ dateHeader,
"",
"-- Timers ----------------------------------------------------------------------",
"test.another.timer",
@@ -234,7 +244,7 @@ public class ConsoleReporterTest {
.convertDurationsTo(TimeUnit.MILLISECONDS)
.filter(MetricFilter.ALL)
.disabledMetricAttributes(disabledMetricAttributes)
- .build();
+ .build();
final Meter meter = mock(Meter.class);
when(meter.getCount()).thenReturn(1L);
@@ -243,15 +253,15 @@ public class ConsoleReporterTest {
when(meter.getFiveMinuteRate()).thenReturn(4.0);
when(meter.getFifteenMinuteRate()).thenReturn(5.0);
- customReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
+ customReporter.report(map(),
+ map(),
+ map(),
map("test.meter", meter),
- this.<Timer>map());
+ map());
assertThat(consoleOutput())
.isEqualTo(lines(
- "3/17/13 6:04:36 PM =============================================================",
+ dateHeader,
"",
"-- Meters ----------------------------------------------------------------------",
"test.meter",
@@ -299,15 +309,15 @@ public class ConsoleReporterTest {
when(timer.getSnapshot()).thenReturn(snapshot);
- customReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
+ customReporter.report(map(),
+ map(),
+ map(),
+ map(),
map("test.another.timer", timer));
assertThat(consoleOutput())
.isEqualTo(lines(
- "3/17/13 6:04:36 PM =============================================================",
+ dateHeader,
"",
"-- Timers ----------------------------------------------------------------------",
"test.another.timer",
@@ -359,15 +369,15 @@ public class ConsoleReporterTest {
when(histogram.getSnapshot()).thenReturn(snapshot);
- customReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
+ customReporter.report(map(),
+ map(),
map("test.histogram", histogram),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map());
assertThat(consoleOutput())
.isEqualTo(lines(
- "3/17/13 6:04:36 PM =============================================================",
+ dateHeader,
"",
"-- Histograms ------------------------------------------------------------------",
"test.histogram",
@@ -396,11 +406,11 @@ public class ConsoleReporterTest {
}
private <T> SortedMap<String, T> map() {
- return new TreeMap<String, T>();
+ return new TreeMap<>();
}
private <T> SortedMap<String, T> map(String name, T metric) {
- final TreeMap<String, T> map = new TreeMap<String, T>();
+ final TreeMap<String, T> map = new TreeMap<>();
map.put(name, metric);
return map;
}
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 f2a3f46..79530b7 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/CounterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/CounterTest.java
@@ -8,13 +8,13 @@ public class CounterTest {
private final Counter counter = new Counter();
@Test
- public void startsAtZero() throws Exception {
+ public void startsAtZero() {
assertThat(counter.getCount())
.isZero();
}
@Test
- public void incrementsByOne() throws Exception {
+ public void incrementsByOne() {
counter.inc();
assertThat(counter.getCount())
@@ -22,7 +22,7 @@ public class CounterTest {
}
@Test
- public void incrementsByAnArbitraryDelta() throws Exception {
+ public void incrementsByAnArbitraryDelta() {
counter.inc(12);
assertThat(counter.getCount())
@@ -30,7 +30,7 @@ public class CounterTest {
}
@Test
- public void decrementsByOne() throws Exception {
+ public void decrementsByOne() {
counter.dec();
assertThat(counter.getCount())
@@ -38,7 +38,7 @@ public class CounterTest {
}
@Test
- public void decrementsByAnArbitraryDelta() throws Exception {
+ public void decrementsByAnArbitraryDelta() {
counter.dec(12);
assertThat(counter.getCount())
@@ -46,7 +46,7 @@ public class CounterTest {
}
@Test
- public void incrementByNegativeDelta() throws Exception {
+ public void incrementByNegativeDelta() {
counter.inc(-12);
assertThat(counter.getCount())
@@ -54,7 +54,7 @@ public class CounterTest {
}
@Test
- public void decrementByNegativeDelta() throws Exception {
+ public void decrementByNegativeDelta() {
counter.dec(-12);
assertThat(counter.getCount())
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 ae40c2f..6ef4cdd 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/CsvReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/CsvReporterTest.java
@@ -7,6 +7,7 @@ import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Locale;
import java.util.SortedMap;
@@ -14,10 +15,13 @@ import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class CsvReporterTest {
- @Rule public final TemporaryFolder folder = new TemporaryFolder();
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
private final MetricRegistry registry = mock(MetricRegistry.class);
private final Clock clock = mock(Clock.class);
@@ -32,24 +36,23 @@ public class CsvReporterTest {
this.dataDirectory = folder.newFolder();
this.reporter = CsvReporter.forRegistry(registry)
- .formatFor(Locale.US)
- .convertRatesTo(TimeUnit.SECONDS)
- .convertDurationsTo(TimeUnit.MILLISECONDS)
- .withClock(clock)
- .filter(MetricFilter.ALL)
- .build(dataDirectory);
+ .formatFor(Locale.US)
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .withClock(clock)
+ .filter(MetricFilter.ALL)
+ .build(dataDirectory);
}
@Test
public void reportsGaugeValues() throws Exception {
- final Gauge gauge = mock(Gauge.class);
- when(gauge.getValue()).thenReturn(1);
+ final Gauge<Integer> gauge = () -> 1;
reporter.report(map("gauge", gauge),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
assertThat(fileContents("gauge.csv"))
.isEqualTo(csv(
@@ -63,11 +66,11 @@ public class CsvReporterTest {
final Counter counter = mock(Counter.class);
when(counter.getCount()).thenReturn(100L);
- reporter.report(this.<Gauge>map(),
- map("test.counter", counter),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ reporter.report(map(),
+ map("test.counter", counter),
+ map(),
+ map(),
+ map());
assertThat(fileContents("test.counter.csv"))
.isEqualTo(csv(
@@ -95,11 +98,11 @@ public class CsvReporterTest {
when(histogram.getSnapshot()).thenReturn(snapshot);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- map("test.histogram", histogram),
- this.<Meter>map(),
- this.<Timer>map());
+ reporter.report(map(),
+ map(),
+ map("test.histogram", histogram),
+ map(),
+ map());
assertThat(fileContents("test.histogram.csv"))
.isEqualTo(csv(
@@ -110,18 +113,13 @@ public class CsvReporterTest {
@Test
public void reportsMeterValues() throws Exception {
- 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);
+ final Meter meter = mockMeter();
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- map("test.meter", meter),
- this.<Timer>map());
+ reporter.report(map(),
+ map(),
+ map(),
+ map("test.meter", meter),
+ map());
assertThat(fileContents("test.meter.csv"))
.isEqualTo(csv(
@@ -153,11 +151,11 @@ public class CsvReporterTest {
when(timer.getSnapshot()).thenReturn(snapshot);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- map("test.another.timer", timer));
+ reporter.report(map(),
+ map(),
+ map(),
+ map(),
+ map("test.another.timer", timer));
assertThat(fileContents("test.another.timer.csv"))
.isEqualTo(csv(
@@ -175,18 +173,54 @@ public class CsvReporterTest {
.withCsvFileProvider(fileProvider)
.build(dataDirectory);
- final Gauge gauge = mock(Gauge.class);
- when(gauge.getValue()).thenReturn(1);
+ final Gauge<Integer> gauge = () -> 1;
reporter.report(map("gauge", gauge),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
verify(fileProvider).getFile(dataDirectory, "gauge");
}
+ @Test
+ public void itFormatsWithCustomSeparator() throws Exception {
+ final Meter meter = mockMeter();
+
+ CsvReporter customSeparatorReporter = CsvReporter.forRegistry(registry)
+ .formatFor(Locale.US)
+ .withSeparator("|")
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .withClock(clock)
+ .filter(MetricFilter.ALL)
+ .build(dataDirectory);
+
+ customSeparatorReporter.report(map(),
+ map(),
+ map(),
+ map("test.meter", meter),
+ map());
+
+ assertThat(fileContents("test.meter.csv"))
+ .isEqualTo(csv(
+ "t|count|mean_rate|m1_rate|m5_rate|m15_rate|rate_unit",
+ "19910191|1|2.000000|3.000000|4.000000|5.000000|events/second"
+ ));
+ }
+
+ private Meter mockMeter() {
+ 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);
+
+ return meter;
+ }
+
private String csv(String... lines) {
final StringBuilder builder = new StringBuilder();
for (String line : lines) {
@@ -196,15 +230,15 @@ public class CsvReporterTest {
}
private String fileContents(String filename) throws IOException {
- return new String(Files.readAllBytes(new File(dataDirectory, filename).toPath()));
+ return new String(Files.readAllBytes(new File(dataDirectory, filename).toPath()), StandardCharsets.UTF_8);
}
private <T> SortedMap<String, T> map() {
- return new TreeMap<String, T>();
+ return new TreeMap<>();
}
private <T> SortedMap<String, T> map(String name, T metric) {
- final TreeMap<String, T> map = new TreeMap<String, T>();
+ final TreeMap<String, T> map = new TreeMap<>();
map.put(name, metric);
return map;
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/DefaultObjectNameFactoryTest.java b/metrics-core/src/test/java/com/codahale/metrics/DefaultObjectNameFactoryTest.java
deleted file mode 100644
index 20c7ad5..0000000
--- a/metrics-core/src/test/java/com/codahale/metrics/DefaultObjectNameFactoryTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.codahale.metrics;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import javax.management.ObjectName;
-
-import org.junit.Test;
-
-public class DefaultObjectNameFactoryTest {
-
- @Test
- public void createsObjectNameWithDomainInInput() {
- DefaultObjectNameFactory f = new DefaultObjectNameFactory();
- ObjectName on = f.createName("type", "com.domain", "something.with.dots");
- assertThat(on.getDomain()).isEqualTo("com.domain");
- }
-
- @Test
- public void createsObjectNameWithNameAsKeyPropertyName() {
- DefaultObjectNameFactory f = new DefaultObjectNameFactory();
- ObjectName on = f.createName("type", "com.domain", "something.with.dots");
- assertThat(on.getKeyProperty("name")).isEqualTo("something.with.dots");
- }
-}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/DefaultSettableGaugeTest.java b/metrics-core/src/test/java/com/codahale/metrics/DefaultSettableGaugeTest.java
new file mode 100644
index 0000000..c6cdb3f
--- /dev/null
+++ b/metrics-core/src/test/java/com/codahale/metrics/DefaultSettableGaugeTest.java
@@ -0,0 +1,26 @@
+package com.codahale.metrics;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultSettableGaugeTest {
+ @Test
+ public void newSettableGaugeWithoutDefaultReturnsNull() {
+ DefaultSettableGauge<String> gauge = new DefaultSettableGauge<>();
+ assertThat(gauge.getValue()).isNull();
+ }
+
+ @Test
+ public void newSettableGaugeWithDefaultReturnsDefault() {
+ DefaultSettableGauge<String> gauge = new DefaultSettableGauge<>("default");
+ assertThat(gauge.getValue()).isEqualTo("default");
+ }
+
+ @Test
+ public void setValueOverwritesExistingValue() {
+ DefaultSettableGauge<String> gauge = new DefaultSettableGauge<>("default");
+ gauge.setValue("test");
+ assertThat(gauge.getValue()).isEqualTo("test");
+ }
+}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/DerivativeGaugeTest.java b/metrics-core/src/test/java/com/codahale/metrics/DerivativeGaugeTest.java
index 8f089fd..1b0761e 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/DerivativeGaugeTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/DerivativeGaugeTest.java
@@ -5,12 +5,7 @@ import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class DerivativeGaugeTest {
- private final Gauge<String> gauge1 = new Gauge<String>() {
- @Override
- public String getValue() {
- return "woo";
- }
- };
+ private final Gauge<String> gauge1 = () -> "woo";
private final Gauge<Integer> gauge2 = new DerivativeGauge<String, Integer>(gauge1) {
@Override
protected Integer transform(String value) {
@@ -19,7 +14,7 @@ public class DerivativeGaugeTest {
};
@Test
- public void returnsATransformedValue() throws Exception {
+ public void returnsATransformedValue() {
assertThat(gauge2.getValue())
.isEqualTo(3);
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/EWMATest.java b/metrics-core/src/test/java/com/codahale/metrics/EWMATest.java
index 9e95235..6c723d9 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/EWMATest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/EWMATest.java
@@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.offset;
public class EWMATest {
@Test
- public void aOneMinuteEWMAWithAValueOfThree() throws Exception {
+ public void aOneMinuteEWMAWithAValueOfThree() {
final EWMA ewma = EWMA.oneMinuteEWMA();
ewma.update(3);
ewma.tick();
@@ -78,7 +78,7 @@ public class EWMATest {
}
@Test
- public void aFiveMinuteEWMAWithAValueOfThree() throws Exception {
+ public void aFiveMinuteEWMAWithAValueOfThree() {
final EWMA ewma = EWMA.fiveMinuteEWMA();
ewma.update(3);
ewma.tick();
@@ -147,7 +147,7 @@ public class EWMATest {
}
@Test
- public void aFifteenMinuteEWMAWithAValueOfThree() throws Exception {
+ public void aFifteenMinuteEWMAWithAValueOfThree() {
final EWMA ewma = EWMA.fifteenMinuteEWMA();
ewma.update(3);
ewma.tick();
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 34b7cbf..8708637 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ExponentiallyDecayingReservoirTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ExponentiallyDecayingReservoirTest.java
@@ -2,17 +2,63 @@ package com.codahale.metrics;
import com.codahale.metrics.Timer.Context;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
+@RunWith(Parameterized.class)
public class ExponentiallyDecayingReservoirTest {
+
+ public enum ReservoirFactory {
+ EXPONENTIALLY_DECAYING() {
+ @Override
+ Reservoir create(int size, double alpha, Clock clock) {
+ return new ExponentiallyDecayingReservoir(size, alpha, clock);
+ }
+ },
+
+ LOCK_FREE_EXPONENTIALLY_DECAYING() {
+ @Override
+ Reservoir create(int size, double alpha, Clock clock) {
+ return LockFreeExponentiallyDecayingReservoir.builder()
+ .size(size)
+ .alpha(alpha)
+ .clock(clock)
+ .build();
+ }
+ };
+
+ abstract Reservoir create(int size, double alpha, Clock clock);
+
+ Reservoir create(int size, double alpha) {
+ return create(size, alpha, Clock.defaultClock());
+ }
+ }
+
+ @Parameterized.Parameters(name = "{index}: {0}")
+ public static Collection<Object[]> reservoirs() {
+ return Arrays.stream(ReservoirFactory.values())
+ .map(value -> new Object[] {value})
+ .collect(Collectors.toList());
+ }
+
+ private final ReservoirFactory reservoirFactory;
+
+ public ExponentiallyDecayingReservoirTest(ReservoirFactory reservoirFactory) {
+ this.reservoirFactory = reservoirFactory;
+ }
+
@Test
- public void aReservoirOf100OutOf1000Elements() throws Exception {
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(100, 0.99);
+ public void aReservoirOf100OutOf1000Elements() {
+ final Reservoir reservoir = reservoirFactory.create(100, 0.99);
for (int i = 0; i < 1000; i++) {
reservoir.update(i);
}
@@ -29,8 +75,8 @@ public class ExponentiallyDecayingReservoirTest {
}
@Test
- public void aReservoirOf100OutOf10Elements() throws Exception {
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(100, 0.99);
+ public void aReservoirOf100OutOf10Elements() {
+ final Reservoir reservoir = reservoirFactory.create(100, 0.99);
for (int i = 0; i < 10; i++) {
reservoir.update(i);
}
@@ -47,8 +93,8 @@ public class ExponentiallyDecayingReservoirTest {
}
@Test
- public void aHeavilyBiasedReservoirOf100OutOf1000Elements() throws Exception {
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000, 0.01);
+ public void aHeavilyBiasedReservoirOf100OutOf1000Elements() {
+ final Reservoir reservoir = reservoirFactory.create(1000, 0.01);
for (int i = 0; i < 100; i++) {
reservoir.update(i);
}
@@ -68,9 +114,7 @@ public class ExponentiallyDecayingReservoirTest {
@Test
public void longPeriodsOfInactivityShouldNotCorruptSamplingState() {
final ManualClock clock = new ManualClock();
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(10,
- 0.015,
- clock);
+ final Reservoir reservoir = reservoirFactory.create(10, 0.15, clock);
// add 1000 values at a rate of 10 values/second
for (int i = 0; i < 1000; i++) {
@@ -82,14 +126,13 @@ public class ExponentiallyDecayingReservoirTest {
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.
+ // this should trigger a rescale. Note that the number of samples will be reduced to 1
+ // because scaling factor equal to zero will remove all existing entries after rescale.
clock.addHours(15);
reservoir.update(2000);
assertThat(reservoir.getSnapshot().size())
.isEqualTo(1);
- assertAllValuesBetween(reservoir, 1000, 3000);
+ assertAllValuesBetween(reservoir, 1000, 2001);
// add 1000 values at a rate of 10 values/second
@@ -105,7 +148,7 @@ public class ExponentiallyDecayingReservoirTest {
@Test
public void longPeriodsOfInactivity_fetchShouldResample() {
final ManualClock clock = new ManualClock();
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(10,
+ final Reservoir reservoir = reservoirFactory.create(10,
0.015,
clock);
@@ -118,12 +161,10 @@ public class ExponentiallyDecayingReservoirTest {
.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.
+ // wait for 20 hours and take snapshot.
+ // this should trigger a rescale. Note that the number of samples will be reduced to 0
+ // because scaling factor equal to zero will remove all existing entries after rescale.
clock.addHours(20);
-
Snapshot snapshot = reservoir.getSnapshot();
assertThat(snapshot.getMax()).isEqualTo(0);
assertThat(snapshot.getMean()).isEqualTo(0);
@@ -133,7 +174,7 @@ public class ExponentiallyDecayingReservoirTest {
@Test
public void emptyReservoirSnapshot_shouldReturnZeroForAllValues() {
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(100, 0.015,
+ final Reservoir reservoir = reservoirFactory.create(100, 0.015,
new ManualClock());
Snapshot snapshot = reservoir.getSnapshot();
@@ -146,7 +187,7 @@ public class ExponentiallyDecayingReservoirTest {
@Test
public void removeZeroWeightsInSamplesToPreventNaNInMeanValues() {
final ManualClock clock = new ManualClock();
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1028, 0.015, clock);
+ final Reservoir reservoir = reservoirFactory.create(1028, 0.015, clock);
Timer timer = new Timer(reservoir, clock);
Context context = timer.time();
@@ -160,7 +201,7 @@ public class ExponentiallyDecayingReservoirTest {
}
@Test
- public void multipleUpdatesAfterlongPeriodsOfInactivityShouldNotCorruptSamplingState () throws Exception {
+ 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
@@ -171,9 +212,9 @@ public class ExponentiallyDecayingReservoirTest {
// expanded.
// Run the test several times.
- for (int attempt=0; attempt < 10; attempt++) {
+ for (int attempt = 0; attempt < 10; attempt++) {
final ManualClock clock = new ManualClock();
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(10,
+ final Reservoir reservoir = reservoirFactory.create(10,
0.015,
clock);
@@ -183,27 +224,26 @@ public class ExponentiallyDecayingReservoirTest {
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;
+ final Thread thread = new Thread(() -> {
+ int previous = 0;
+ while (running.get()) {
+ // Wait for the test thread to update it's counter
+ // before updaing the reservoir.
+ while (true) {
+ int next = testUpdates.get();
+ if (previous < next) {
+ previous = next;
+ break;
+ }
+ }
- // Update the reservoir. This needs to occur at the
- // same time as the test thread's update.
- reservoir.update(1000);
+ // 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();
- }
+ // Signal the main thread; allows the next update
+ // attempt to begin.
+ threadUpdates.incrementAndGet();
}
});
@@ -212,7 +252,7 @@ public class ExponentiallyDecayingReservoirTest {
int sum = 0;
int previous = -1;
for (int i = 0; i < 100; i++) {
- // Wait for 24 hours before attempting the next concurrent
+ // Wait for 15 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:
@@ -232,11 +272,13 @@ public class ExponentiallyDecayingReservoirTest {
reservoir.update(1000);
// Wait for the other thread to finish it's update.
- int next;
- while (previous >= (next = threadUpdates.get()))
- ; // spin lock
-
- previous = next;
+ while (true) {
+ int next = threadUpdates.get();
+ if (previous < next) {
+ previous = next;
+ break;
+ }
+ }
}
// Terminate the thread.
@@ -257,20 +299,20 @@ public class ExponentiallyDecayingReservoirTest {
@Test
public void spotLift() {
final ManualClock clock = new ManualClock();
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000,
- 0.015,
- clock);
+ final Reservoir reservoir = reservoirFactory.create(1000,
+ 0.015,
+ clock);
final int valuesRatePerMinute = 10;
final int valuesIntervalMillis = (int) (TimeUnit.MINUTES.toMillis(1) / valuesRatePerMinute);
// mode 1: steady regime for 120 minutes
- for (int i = 0; i < 120*valuesRatePerMinute; i++) {
+ for (int i = 0; i < 120 * valuesRatePerMinute; i++) {
reservoir.update(177);
clock.addMillis(valuesIntervalMillis);
}
// switching to mode 2: 10 minutes more with the same rate, but larger value
- for (int i = 0; i < 10*valuesRatePerMinute; i++) {
+ for (int i = 0; i < 10 * valuesRatePerMinute; i++) {
reservoir.update(9999);
clock.addMillis(valuesIntervalMillis);
}
@@ -283,20 +325,20 @@ public class ExponentiallyDecayingReservoirTest {
@Test
public void spotFall() {
final ManualClock clock = new ManualClock();
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000,
- 0.015,
- clock);
+ final Reservoir reservoir = reservoirFactory.create(1000,
+ 0.015,
+ clock);
final int valuesRatePerMinute = 10;
final int valuesIntervalMillis = (int) (TimeUnit.MINUTES.toMillis(1) / valuesRatePerMinute);
// mode 1: steady regime for 120 minutes
- for (int i = 0; i < 120*valuesRatePerMinute; i++) {
+ for (int i = 0; i < 120 * valuesRatePerMinute; i++) {
reservoir.update(9998);
clock.addMillis(valuesIntervalMillis);
}
// switching to mode 2: 10 minutes more with the same rate, but smaller value
- for (int i = 0; i < 10*valuesRatePerMinute; i++) {
+ for (int i = 0; i < 10 * valuesRatePerMinute; i++) {
reservoir.update(178);
clock.addMillis(valuesIntervalMillis);
}
@@ -309,9 +351,9 @@ public class ExponentiallyDecayingReservoirTest {
@Test
public void quantiliesShouldBeBasedOnWeights() {
final ManualClock clock = new ManualClock();
- final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000,
- 0.015,
- clock);
+ final Reservoir reservoir = reservoirFactory.create(1000,
+ 0.015,
+ clock);
for (int i = 0; i < 40; i++) {
reservoir.update(177);
}
@@ -334,7 +376,35 @@ public class ExponentiallyDecayingReservoirTest {
.isEqualTo(9999);
}
- private static void assertAllValuesBetween(ExponentiallyDecayingReservoir reservoir,
+ @Test
+ public void clockWrapShouldNotRescale() {
+ // First verify the test works as expected given low values
+ testShortPeriodShouldNotRescale(0);
+ // Now revalidate using an edge case nanoTime value just prior to wrapping
+ testShortPeriodShouldNotRescale(Long.MAX_VALUE - TimeUnit.MINUTES.toNanos(30));
+ }
+
+ private void testShortPeriodShouldNotRescale(long startTimeNanos) {
+ final ManualClock clock = new ManualClock(startTimeNanos);
+ final Reservoir reservoir = reservoirFactory.create(10, 1, clock);
+
+ reservoir.update(1000);
+ assertThat(reservoir.getSnapshot().size()).isEqualTo(1);
+
+ assertAllValuesBetween(reservoir, 1000, 1001);
+
+ // wait for 10 millis and take snapshot.
+ // this should not trigger a rescale. Note that the number of samples will be reduced to 0
+ // because scaling factor equal to zero will remove all existing entries after rescale.
+ clock.addSeconds(20 * 60);
+ Snapshot snapshot = reservoir.getSnapshot();
+ assertThat(snapshot.getMax()).isEqualTo(1000);
+ assertThat(snapshot.getMean()).isEqualTo(1000);
+ assertThat(snapshot.getMedian()).isEqualTo(1000);
+ assertThat(snapshot.size()).isEqualTo(1);
+ }
+
+ private static void assertAllValuesBetween(Reservoir reservoir,
double min,
double max) {
for (double i : reservoir.getSnapshot().getValues()) {
diff --git a/metrics-core/src/test/java/com/codahale/metrics/HistogramTest.java b/metrics-core/src/test/java/com/codahale/metrics/HistogramTest.java
index fa39326..17529eb 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/HistogramTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/HistogramTest.java
@@ -3,14 +3,16 @@ package com.codahale.metrics;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class HistogramTest {
private final Reservoir reservoir = mock(Reservoir.class);
private final Histogram histogram = new Histogram(reservoir);
@Test
- public void updatesTheCountOnUpdates() throws Exception {
+ public void updatesTheCountOnUpdates() {
assertThat(histogram.getCount())
.isZero();
@@ -21,7 +23,7 @@ public class HistogramTest {
}
@Test
- public void returnsTheSnapshotFromTheReservoir() throws Exception {
+ public void returnsTheSnapshotFromTheReservoir() {
final Snapshot snapshot = mock(Snapshot.class);
when(reservoir.getSnapshot()).thenReturn(snapshot);
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 4e8eebd..76e026b 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/InstrumentedExecutorServiceTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/InstrumentedExecutorServiceTest.java
@@ -1,13 +1,19 @@
package com.codahale.metrics;
import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.time.Duration;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@@ -15,31 +21,53 @@ import static org.assertj.core.api.Assertions.assertThat;
public class InstrumentedExecutorServiceTest {
private static final Logger LOGGER = LoggerFactory.getLogger(InstrumentedExecutorServiceTest.class);
+ private ExecutorService executor;
+ private MetricRegistry registry;
+ private InstrumentedExecutorService instrumentedExecutorService;
+ private Meter submitted;
+ private Counter running;
+ private Meter completed;
+ private Timer duration;
+ private Timer idle;
- private final ExecutorService executor = Executors.newCachedThreadPool();
- private final MetricRegistry registry = new MetricRegistry();
- private final InstrumentedExecutorService instrumentedExecutorService = new InstrumentedExecutorService(executor, registry, "xs");
+ @Before
+ public void setup() {
+ executor = Executors.newCachedThreadPool();
+ registry = new MetricRegistry();
+ instrumentedExecutorService = new InstrumentedExecutorService(executor, registry, "xs");
+ submitted = registry.meter("xs.submitted");
+ running = registry.counter("xs.running");
+ completed = registry.meter("xs.completed");
+ duration = registry.timer("xs.duration");
+ idle = registry.timer("xs.idle");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ instrumentedExecutorService.shutdown();
+ if (!instrumentedExecutorService.awaitTermination(2, TimeUnit.SECONDS)) {
+ LOGGER.error("InstrumentedExecutorService did not terminate.");
+ }
+ }
@Test
- public void reportsTasksInformation() throws Exception {
- final Meter submitted = registry.meter("xs.submitted");
- final Counter running = registry.counter("xs.running");
- final Meter completed = registry.meter("xs.completed");
- final Timer duration = registry.timer("xs.duration");
+ public void reportsTasksInformationForRunnable() throws Exception {
assertThat(submitted.getCount()).isEqualTo(0);
assertThat(running.getCount()).isEqualTo(0);
assertThat(completed.getCount()).isEqualTo(0);
assertThat(duration.getCount()).isEqualTo(0);
+ assertThat(idle.getCount()).isEqualTo(0);
- Future<?> theFuture = instrumentedExecutorService.submit(new Runnable() {
- public void run() {
- assertThat(submitted.getCount()).isEqualTo(1);
- assertThat(running.getCount()).isEqualTo(1);
- assertThat(completed.getCount()).isEqualTo(0);
- assertThat(duration.getCount()).isEqualTo(0);
- }
- });
+ Runnable runnable = () -> {
+ assertThat(submitted.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(1);
+ assertThat(completed.getCount()).isEqualTo(0);
+ assertThat(duration.getCount()).isEqualTo(0);
+ assertThat(idle.getCount()).isEqualTo(1);
+ };
+
+ Future<?> theFuture = instrumentedExecutorService.submit(runnable);
theFuture.get();
@@ -48,14 +76,143 @@ public class InstrumentedExecutorServiceTest {
assertThat(completed.getCount()).isEqualTo(1);
assertThat(duration.getCount()).isEqualTo(1);
assertThat(duration.getSnapshot().size()).isEqualTo(1);
+ assertThat(idle.getCount()).isEqualTo(1);
+ assertThat(idle.getSnapshot().size()).isEqualTo(1);
}
- @After
- public void tearDown() throws Exception {
- instrumentedExecutorService.shutdown();
- if (!instrumentedExecutorService.awaitTermination(2, TimeUnit.SECONDS)) {
- LOGGER.error("InstrumentedExecutorService did not terminate.");
- }
+ @Test
+ public void reportsTasksInformationForCallable() throws Exception {
+
+ assertThat(submitted.getCount()).isEqualTo(0);
+ assertThat(running.getCount()).isEqualTo(0);
+ assertThat(completed.getCount()).isEqualTo(0);
+ assertThat(duration.getCount()).isEqualTo(0);
+ assertThat(idle.getCount()).isEqualTo(0);
+
+ Callable<Void> callable = () -> {
+ assertThat(submitted.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(1);
+ assertThat(completed.getCount()).isEqualTo(0);
+ assertThat(duration.getCount()).isEqualTo(0);
+ assertThat(idle.getCount()).isEqualTo(1);
+ return null;
+ };
+
+ Future<?> theFuture = instrumentedExecutorService.submit(callable);
+
+ theFuture.get();
+
+ assertThat(submitted.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(0);
+ assertThat(completed.getCount()).isEqualTo(1);
+ assertThat(duration.getCount()).isEqualTo(1);
+ assertThat(duration.getSnapshot().size()).isEqualTo(1);
+ assertThat(idle.getCount()).isEqualTo(1);
+ assertThat(idle.getSnapshot().size()).isEqualTo(1);
}
+ @Test
+ @SuppressWarnings("unchecked")
+ public void reportsTasksInformationForThreadPoolExecutor() throws Exception {
+ executor = new ThreadPoolExecutor(4, 16,
+ 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(32));
+ instrumentedExecutorService = new InstrumentedExecutorService(executor, registry, "tp");
+ submitted = registry.meter("tp.submitted");
+ running = registry.counter("tp.running");
+ completed = registry.meter("tp.completed");
+ duration = registry.timer("tp.duration");
+ idle = registry.timer("tp.idle");
+ final Gauge<Integer> poolSize = (Gauge<Integer>) registry.getGauges().get("tp.pool.size");
+ final Gauge<Integer> poolCoreSize = (Gauge<Integer>) registry.getGauges().get("tp.pool.core");
+ final Gauge<Integer> poolMaxSize = (Gauge<Integer>) registry.getGauges().get("tp.pool.max");
+ final Gauge<Integer> tasksActive = (Gauge<Integer>) registry.getGauges().get("tp.tasks.active");
+ final Gauge<Long> tasksCompleted = (Gauge<Long>) registry.getGauges().get("tp.tasks.completed");
+ final Gauge<Integer> tasksQueued = (Gauge<Integer>) registry.getGauges().get("tp.tasks.queued");
+ final Gauge<Integer> tasksCapacityRemaining = (Gauge<Integer>) registry.getGauges().get("tp.tasks.capacity");
+
+ assertThat(submitted.getCount()).isEqualTo(0);
+ assertThat(running.getCount()).isEqualTo(0);
+ assertThat(completed.getCount()).isEqualTo(0);
+ assertThat(duration.getCount()).isEqualTo(0);
+ assertThat(idle.getCount()).isEqualTo(0);
+ assertThat(poolSize.getValue()).isEqualTo(0);
+ assertThat(poolCoreSize.getValue()).isEqualTo(4);
+ assertThat(poolMaxSize.getValue()).isEqualTo(16);
+ assertThat(tasksActive.getValue()).isEqualTo(0);
+ assertThat(tasksCompleted.getValue()).isEqualTo(0L);
+ assertThat(tasksQueued.getValue()).isEqualTo(0);
+ assertThat(tasksCapacityRemaining.getValue()).isEqualTo(32);
+
+ Runnable runnable = () -> {
+ assertThat(submitted.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(1);
+ assertThat(completed.getCount()).isEqualTo(0);
+ assertThat(duration.getCount()).isEqualTo(0);
+ assertThat(idle.getCount()).isEqualTo(1);
+ assertThat(tasksActive.getValue()).isEqualTo(1);
+ assertThat(tasksQueued.getValue()).isEqualTo(0);
+ };
+
+ Future<?> theFuture = instrumentedExecutorService.submit(runnable);
+
+ assertThat(theFuture).succeedsWithin(Duration.ofSeconds(5L));
+
+ assertThat(submitted.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(0);
+ assertThat(completed.getCount()).isEqualTo(1);
+ assertThat(duration.getCount()).isEqualTo(1);
+ assertThat(duration.getSnapshot().size()).isEqualTo(1);
+ assertThat(idle.getCount()).isEqualTo(1);
+ assertThat(idle.getSnapshot().size()).isEqualTo(1);
+ assertThat(poolSize.getValue()).isEqualTo(1);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void reportsTasksInformationForForkJoinPool() throws Exception {
+ executor = Executors.newWorkStealingPool(4);
+ instrumentedExecutorService = new InstrumentedExecutorService(executor, registry, "fjp");
+ submitted = registry.meter("fjp.submitted");
+ running = registry.counter("fjp.running");
+ completed = registry.meter("fjp.completed");
+ duration = registry.timer("fjp.duration");
+ idle = registry.timer("fjp.idle");
+ final Gauge<Long> tasksStolen = (Gauge<Long>) registry.getGauges().get("fjp.tasks.stolen");
+ final Gauge<Long> tasksQueued = (Gauge<Long>) registry.getGauges().get("fjp.tasks.queued");
+ final Gauge<Integer> threadsActive = (Gauge<Integer>) registry.getGauges().get("fjp.threads.active");
+ final Gauge<Integer> threadsRunning = (Gauge<Integer>) registry.getGauges().get("fjp.threads.running");
+
+ assertThat(submitted.getCount()).isEqualTo(0);
+ assertThat(running.getCount()).isEqualTo(0);
+ assertThat(completed.getCount()).isEqualTo(0);
+ assertThat(duration.getCount()).isEqualTo(0);
+ assertThat(idle.getCount()).isEqualTo(0);
+ assertThat(tasksStolen.getValue()).isEqualTo(0L);
+ assertThat(tasksQueued.getValue()).isEqualTo(0L);
+ assertThat(threadsActive.getValue()).isEqualTo(0);
+ assertThat(threadsRunning.getValue()).isEqualTo(0);
+
+ Runnable runnable = () -> {
+ assertThat(submitted.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(1);
+ assertThat(completed.getCount()).isEqualTo(0);
+ assertThat(duration.getCount()).isEqualTo(0);
+ assertThat(idle.getCount()).isEqualTo(1);
+ assertThat(tasksQueued.getValue()).isEqualTo(0L);
+ assertThat(threadsActive.getValue()).isEqualTo(1);
+ assertThat(threadsRunning.getValue()).isEqualTo(1);
+ };
+
+ Future<?> theFuture = instrumentedExecutorService.submit(runnable);
+
+ assertThat(theFuture).succeedsWithin(Duration.ofSeconds(5L));
+
+ assertThat(submitted.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(0);
+ assertThat(completed.getCount()).isEqualTo(1);
+ assertThat(duration.getCount()).isEqualTo(1);
+ assertThat(duration.getSnapshot().size()).isEqualTo(1);
+ assertThat(idle.getCount()).isEqualTo(1);
+ assertThat(idle.getSnapshot().size()).isEqualTo(1);
+ }
}
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 9e9a246..f8ab4fd 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/InstrumentedScheduledExecutorServiceTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/InstrumentedScheduledExecutorServiceTest.java
@@ -5,7 +5,12 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.concurrent.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@@ -16,16 +21,16 @@ public class InstrumentedScheduledExecutorServiceTest {
private final MetricRegistry registry = new MetricRegistry();
private final InstrumentedScheduledExecutorService instrumentedScheduledExecutor = new InstrumentedScheduledExecutorService(scheduledExecutor, registry, "xs");
- final Meter submitted = registry.meter("xs.submitted");
+ private final Meter submitted = registry.meter("xs.submitted");
- final Counter running = registry.counter("xs.running");
- final Meter completed = registry.meter("xs.completed");
- final Timer duration = registry.timer("xs.duration");
+ private final Counter running = registry.counter("xs.running");
+ private final Meter completed = registry.meter("xs.completed");
+ private final Timer duration = registry.timer("xs.duration");
- final Meter scheduledOnce = registry.meter("xs.scheduled.once");
- final Meter scheduledRepetitively = registry.meter("xs.scheduled.repetitively");
- final Counter scheduledOverrun = registry.counter("xs.scheduled.overrun");
- final Histogram percentOfPeriod = registry.histogram("xs.scheduled.percent-of-period");
+ private final Meter scheduledOnce = registry.meter("xs.scheduled.once");
+ private final Meter scheduledRepetitively = registry.meter("xs.scheduled.repetitively");
+ private final Counter scheduledOverrun = registry.counter("xs.scheduled.overrun");
+ private final Histogram percentOfPeriod = registry.histogram("xs.scheduled.percent-of-period");
@Test
public void testSubmitRunnable() throws Exception {
@@ -40,19 +45,17 @@ public class InstrumentedScheduledExecutorServiceTest {
assertThat(scheduledOverrun.getCount()).isZero();
assertThat(percentOfPeriod.getCount()).isZero();
- Future<?> theFuture = instrumentedScheduledExecutor.submit(new Runnable() {
- public void run() {
- assertThat(submitted.getCount()).isEqualTo(1);
+ Future<?> theFuture = instrumentedScheduledExecutor.submit(() -> {
+ assertThat(submitted.getCount()).isEqualTo(1);
- assertThat(running.getCount()).isEqualTo(1);
- assertThat(completed.getCount()).isZero();
- assertThat(duration.getCount()).isZero();
+ assertThat(running.getCount()).isEqualTo(1);
+ assertThat(completed.getCount()).isZero();
+ assertThat(duration.getCount()).isZero();
- assertThat(scheduledOnce.getCount()).isZero();
- assertThat(scheduledRepetitively.getCount()).isZero();
- assertThat(scheduledOverrun.getCount()).isZero();
- assertThat(percentOfPeriod.getCount()).isZero();
- }
+ assertThat(scheduledOnce.getCount()).isZero();
+ assertThat(scheduledRepetitively.getCount()).isZero();
+ assertThat(scheduledOverrun.getCount()).isZero();
+ assertThat(percentOfPeriod.getCount()).isZero();
});
theFuture.get();
@@ -83,19 +86,17 @@ public class InstrumentedScheduledExecutorServiceTest {
assertThat(scheduledOverrun.getCount()).isZero();
assertThat(percentOfPeriod.getCount()).isZero();
- ScheduledFuture<?> theFuture = instrumentedScheduledExecutor.schedule(new Runnable() {
- public void run() {
- assertThat(submitted.getCount()).isZero();
+ ScheduledFuture<?> theFuture = instrumentedScheduledExecutor.schedule(() -> {
+ assertThat(submitted.getCount()).isZero();
- assertThat(running.getCount()).isEqualTo(1);
- assertThat(completed.getCount()).isZero();
- assertThat(duration.getCount()).isZero();
+ assertThat(running.getCount()).isEqualTo(1);
+ assertThat(completed.getCount()).isZero();
+ assertThat(duration.getCount()).isZero();
- assertThat(scheduledOnce.getCount()).isEqualTo(1);
- assertThat(scheduledRepetitively.getCount()).isZero();
- assertThat(scheduledOverrun.getCount()).isZero();
- assertThat(percentOfPeriod.getCount()).isZero();
- }
+ assertThat(scheduledOnce.getCount()).isEqualTo(1);
+ assertThat(scheduledRepetitively.getCount()).isZero();
+ assertThat(scheduledOverrun.getCount()).isZero();
+ assertThat(percentOfPeriod.getCount()).isZero();
}, 10L, TimeUnit.MILLISECONDS);
theFuture.get();
@@ -128,21 +129,19 @@ public class InstrumentedScheduledExecutorServiceTest {
final Object obj = new Object();
- Future<Object> theFuture = instrumentedScheduledExecutor.submit(new Callable<Object>() {
- public Object call() {
- assertThat(submitted.getCount()).isEqualTo(1);
+ Future<Object> theFuture = instrumentedScheduledExecutor.submit(() -> {
+ assertThat(submitted.getCount()).isEqualTo(1);
- assertThat(running.getCount()).isEqualTo(1);
- assertThat(completed.getCount()).isZero();
- assertThat(duration.getCount()).isZero();
+ assertThat(running.getCount()).isEqualTo(1);
+ assertThat(completed.getCount()).isZero();
+ assertThat(duration.getCount()).isZero();
- assertThat(scheduledOnce.getCount()).isZero();
- assertThat(scheduledRepetitively.getCount()).isZero();
- assertThat(scheduledOverrun.getCount()).isZero();
- assertThat(percentOfPeriod.getCount()).isZero();
+ assertThat(scheduledOnce.getCount()).isZero();
+ assertThat(scheduledRepetitively.getCount()).isZero();
+ assertThat(scheduledOverrun.getCount()).isZero();
+ assertThat(percentOfPeriod.getCount()).isZero();
- return obj;
- }
+ return obj;
});
assertThat(theFuture.get()).isEqualTo(obj);
@@ -175,21 +174,19 @@ public class InstrumentedScheduledExecutorServiceTest {
final Object obj = new Object();
- ScheduledFuture<Object> theFuture = instrumentedScheduledExecutor.schedule(new Callable<Object>() {
- public Object call() {
- assertThat(submitted.getCount()).isZero();
+ ScheduledFuture<Object> theFuture = instrumentedScheduledExecutor.schedule(() -> {
+ assertThat(submitted.getCount()).isZero();
- assertThat(running.getCount()).isEqualTo(1);
- assertThat(completed.getCount()).isZero();
- assertThat(duration.getCount()).isZero();
+ assertThat(running.getCount()).isEqualTo(1);
+ assertThat(completed.getCount()).isZero();
+ assertThat(duration.getCount()).isZero();
- assertThat(scheduledOnce.getCount()).isEqualTo(1);
- assertThat(scheduledRepetitively.getCount()).isZero();
- assertThat(scheduledOverrun.getCount()).isZero();
- assertThat(percentOfPeriod.getCount()).isZero();
+ assertThat(scheduledOnce.getCount()).isEqualTo(1);
+ assertThat(scheduledRepetitively.getCount()).isZero();
+ assertThat(scheduledOverrun.getCount()).isZero();
+ assertThat(percentOfPeriod.getCount()).isZero();
- return obj;
- }
+ return obj;
}, 10L, TimeUnit.MILLISECONDS);
assertThat(theFuture.get()).isEqualTo(obj);
@@ -220,29 +217,26 @@ public class InstrumentedScheduledExecutorServiceTest {
assertThat(scheduledOverrun.getCount()).isZero();
assertThat(percentOfPeriod.getCount()).isZero();
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- ScheduledFuture<?> theFuture = instrumentedScheduledExecutor.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- assertThat(submitted.getCount()).isZero();
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ ScheduledFuture<?> theFuture = instrumentedScheduledExecutor.scheduleAtFixedRate(() -> {
+ assertThat(submitted.getCount()).isZero();
- assertThat(running.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(1);
- assertThat(scheduledOnce.getCount()).isEqualTo(0);
- assertThat(scheduledRepetitively.getCount()).isEqualTo(1);
+ assertThat(scheduledOnce.getCount()).isEqualTo(0);
+ assertThat(scheduledRepetitively.getCount()).isEqualTo(1);
- try {
- TimeUnit.MILLISECONDS.sleep(50);
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
- countDownLatch.countDown();
+ try {
+ TimeUnit.MILLISECONDS.sleep(50);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
}
+ countDownLatch.countDown();
}, 10L, 10L, TimeUnit.MILLISECONDS);
TimeUnit.MILLISECONDS.sleep(100); // Give some time for the task to be run
countDownLatch.await(5, TimeUnit.SECONDS); // Don't cancel until it didn't complete once
theFuture.cancel(true);
- TimeUnit.MILLISECONDS.sleep(100); // Wait while the task is cancelled
+ TimeUnit.MILLISECONDS.sleep(200); // Wait while the task is cancelled
assertThat(submitted.getCount()).isZero();
@@ -270,30 +264,27 @@ public class InstrumentedScheduledExecutorServiceTest {
assertThat(scheduledOverrun.getCount()).isZero();
assertThat(percentOfPeriod.getCount()).isZero();
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- ScheduledFuture<?> theFuture = instrumentedScheduledExecutor.scheduleWithFixedDelay(new Runnable() {
- @Override
- public void run() {
- assertThat(submitted.getCount()).isZero();
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ ScheduledFuture<?> theFuture = instrumentedScheduledExecutor.scheduleWithFixedDelay(() -> {
+ assertThat(submitted.getCount()).isZero();
- assertThat(running.getCount()).isEqualTo(1);
+ assertThat(running.getCount()).isEqualTo(1);
- assertThat(scheduledOnce.getCount()).isEqualTo(0);
- assertThat(scheduledRepetitively.getCount()).isEqualTo(1);
+ assertThat(scheduledOnce.getCount()).isEqualTo(0);
+ assertThat(scheduledRepetitively.getCount()).isEqualTo(1);
- try {
- TimeUnit.MILLISECONDS.sleep(50);
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
- countDownLatch.countDown();
+ try {
+ TimeUnit.MILLISECONDS.sleep(50);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
}
+ countDownLatch.countDown();
}, 10L, 10L, TimeUnit.MILLISECONDS);
TimeUnit.MILLISECONDS.sleep(100);
countDownLatch.await(5, TimeUnit.SECONDS);
theFuture.cancel(true);
- TimeUnit.MILLISECONDS.sleep(100);
+ TimeUnit.MILLISECONDS.sleep(200);
assertThat(submitted.getCount()).isZero();
diff --git a/metrics-core/src/test/java/com/codahale/metrics/InstrumentedThreadFactoryTest.java b/metrics-core/src/test/java/com/codahale/metrics/InstrumentedThreadFactoryTest.java
index 387569c..61325f0 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/InstrumentedThreadFactoryTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/InstrumentedThreadFactoryTest.java
@@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -22,38 +23,14 @@ public class InstrumentedThreadFactoryTest {
/**
* Tests all parts of the InstrumentedThreadFactory except for termination since that
* is currently difficult to do without race conditions.
- *
* TODO: Try not using real threads in a unit test?
*/
@Test
public void reportsThreadInformation() throws Exception {
- final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
- final Object lock = new Object();
+ final CountDownLatch allTasksAreCreated = new CountDownLatch(THREAD_COUNT);
+ final CountDownLatch allTasksAreCounted = new CountDownLatch(1);
final AtomicInteger interrupted = new AtomicInteger();
- /*
- * Implements a runnable that notifies a latch after locking 'lock'.
- * This asserts that all threads have to enter the critical block before the
- * testing thread notifies all.
- *
- * We have to do this to guarantee that the thread pool has 10 LIVE threads
- * before we check the 'created' Meter.
- */
- Runnable fastOne = new Runnable() {
- @Override
- public void run() {
- synchronized (lock) {
- latch.countDown();
-
- try {
- lock.wait();
- } catch (InterruptedException e) {
- interrupted.incrementAndGet();
- }
- }
- }
- };
-
Meter created = registry.meter("factory.created");
Meter terminated = registry.meter("factory.terminated");
@@ -62,15 +39,24 @@ public class InstrumentedThreadFactoryTest {
// generate demand so the executor service creates the threads through our factory.
for (int i = 0; i < THREAD_COUNT + 1; i++) {
- executor.submit(fastOne);
+ Future<?> t = executor.submit(() -> {
+ allTasksAreCreated.countDown();
+
+ // This asserts that all threads have wait wail the testing thread notifies all.
+ // We have to do this to guarantee that the thread pool has 10 LIVE threads
+ // before we check the 'created' Meter.
+ try {
+ allTasksAreCounted.await();
+ } catch (InterruptedException e) {
+ interrupted.incrementAndGet();
+ Thread.currentThread().interrupt();
+ }
+ });
+ assertThat(t).isNotNull();
}
- latch.await(1, TimeUnit.SECONDS);
-
- synchronized (lock) {
- // wake up all threads.
- lock.notifyAll();
- }
+ allTasksAreCreated.await(1, TimeUnit.SECONDS);
+ allTasksAreCounted.countDown();
assertThat(created.getCount()).isEqualTo(10);
assertThat(terminated.getCount()).isEqualTo(0);
diff --git a/metrics-core/src/test/java/com/codahale/metrics/ManualClock.java b/metrics-core/src/test/java/com/codahale/metrics/ManualClock.java
index b7a8f44..0310921 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ManualClock.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ManualClock.java
@@ -3,7 +3,17 @@ package com.codahale.metrics;
import java.util.concurrent.TimeUnit;
public class ManualClock extends Clock {
- long ticksInNanos = 0;
+ private final long initialTicksInNanos;
+ long ticksInNanos;
+
+ public ManualClock(long initialTicksInNanos) {
+ this.initialTicksInNanos = initialTicksInNanos;
+ this.ticksInNanos = initialTicksInNanos;
+ }
+
+ public ManualClock() {
+ this(0L);
+ }
public synchronized void addNanos(long nanos) {
ticksInNanos += nanos;
@@ -12,11 +22,11 @@ public class ManualClock extends Clock {
public synchronized void addSeconds(long seconds) {
ticksInNanos += TimeUnit.SECONDS.toNanos(seconds);
}
-
+
public synchronized void addMillis(long millis) {
ticksInNanos += TimeUnit.MILLISECONDS.toNanos(millis);
}
-
+
public synchronized void addHours(long hours) {
ticksInNanos += TimeUnit.HOURS.toNanos(hours);
}
@@ -28,7 +38,7 @@ public class ManualClock extends Clock {
@Override
public synchronized long getTime() {
- return TimeUnit.NANOSECONDS.toMillis(ticksInNanos);
+ return TimeUnit.NANOSECONDS.toMillis(ticksInNanos - initialTicksInNanos);
}
-
+
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/MeterApproximationTest.java b/metrics-core/src/test/java/com/codahale/metrics/MeterApproximationTest.java
index d008823..eb3560b 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/MeterApproximationTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/MeterApproximationTest.java
@@ -17,66 +17,66 @@ public class MeterApproximationTest {
@Parameters
public static Collection<Object[]> ratesPerMinute() {
- Object[][] data = new Object[][] {
- { 15 }, { 60 }, { 600 }, { 6000 }
+ Object[][] data = new Object[][]{
+ {15}, {60}, {600}, {6000}
};
return Arrays.asList(data);
- }
-
+ }
+
private final long ratePerMinute;
-
+
public MeterApproximationTest(long ratePerMinute) {
this.ratePerMinute = ratePerMinute;
}
-
+
@Test
- public void controlMeter1MinuteMeanApproximation() throws Exception {
+ public void controlMeter1MinuteMeanApproximation() {
final Meter meter = simulateMetronome(
62934, TimeUnit.MILLISECONDS,
3, TimeUnit.MINUTES);
- assertThat(meter.getOneMinuteRate()*60.0)
- .isEqualTo(ratePerMinute, offset(0.1*ratePerMinute));
+ assertThat(meter.getOneMinuteRate() * 60.0)
+ .isEqualTo(ratePerMinute, offset(0.1 * ratePerMinute));
}
@Test
- public void controlMeter5MinuteMeanApproximation() throws Exception {
+ public void controlMeter5MinuteMeanApproximation() {
final Meter meter = simulateMetronome(
62934, TimeUnit.MILLISECONDS,
13, TimeUnit.MINUTES);
- assertThat(meter.getFiveMinuteRate()*60.0)
- .isEqualTo(ratePerMinute, offset(0.1*ratePerMinute));
+ assertThat(meter.getFiveMinuteRate() * 60.0)
+ .isEqualTo(ratePerMinute, offset(0.1 * ratePerMinute));
}
@Test
- public void controlMeter15MinuteMeanApproximation() throws Exception {
+ public void controlMeter15MinuteMeanApproximation() {
final Meter meter = simulateMetronome(
62934, TimeUnit.MILLISECONDS,
38, TimeUnit.MINUTES);
- assertThat(meter.getFifteenMinuteRate()*60.0)
- .isEqualTo(ratePerMinute, offset(0.1*ratePerMinute));
+ assertThat(meter.getFifteenMinuteRate() * 60.0)
+ .isEqualTo(ratePerMinute, offset(0.1 * ratePerMinute));
}
private Meter simulateMetronome(
long introDelay, TimeUnit introDelayUnit,
long duration, TimeUnit durationUnit) {
-
+
final ManualClock clock = new ManualClock();
final Meter meter = new Meter(clock);
-
+
clock.addNanos(introDelayUnit.toNanos(introDelay));
-
+
final long endTick = clock.getTick() + durationUnit.toNanos(duration);
final long marksIntervalInNanos = TimeUnit.MINUTES.toNanos(1) / ratePerMinute;
-
+
while (clock.getTick() <= endTick) {
clock.addNanos(marksIntervalInNanos);
meter.mark();
}
-
+
return meter;
}
-
+
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/MeterTest.java b/metrics-core/src/test/java/com/codahale/metrics/MeterTest.java
index da4345b..a1c4935 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/MeterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/MeterTest.java
@@ -21,7 +21,7 @@ public class MeterTest {
}
@Test
- public void startsOutWithNoRatesOrCount() throws Exception {
+ public void startsOutWithNoRatesOrCount() {
assertThat(meter.getCount())
.isZero();
@@ -39,7 +39,7 @@ public class MeterTest {
}
@Test
- public void marksEventsAndUpdatesRatesAndCount() throws Exception {
+ public void marksEventsAndUpdatesRatesAndCount() {
meter.mark();
meter.mark(2);
diff --git a/metrics-core/src/test/java/com/codahale/metrics/MetricFilterTest.java b/metrics-core/src/test/java/com/codahale/metrics/MetricFilterTest.java
index 978e7ef..3ae0363 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/MetricFilterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/MetricFilterTest.java
@@ -7,8 +7,32 @@ import static org.mockito.Mockito.mock;
public class MetricFilterTest {
@Test
- public void theAllFilterMatchesAllMetrics() throws Exception {
+ public void theAllFilterMatchesAllMetrics() {
assertThat(MetricFilter.ALL.matches("", mock(Metric.class)))
.isTrue();
}
+
+ @Test
+ public void theStartsWithFilterMatches() {
+ assertThat(MetricFilter.startsWith("foo").matches("foo.bar", mock(Metric.class)))
+ .isTrue();
+ assertThat(MetricFilter.startsWith("foo").matches("bar.foo", mock(Metric.class)))
+ .isFalse();
+ }
+
+ @Test
+ public void theEndsWithFilterMatches() {
+ assertThat(MetricFilter.endsWith("foo").matches("foo.bar", mock(Metric.class)))
+ .isFalse();
+ assertThat(MetricFilter.endsWith("foo").matches("bar.foo", mock(Metric.class)))
+ .isTrue();
+ }
+
+ @Test
+ public void theContainsFilterMatches() {
+ assertThat(MetricFilter.contains("foo").matches("bar.foo.bar", mock(Metric.class)))
+ .isTrue();
+ assertThat(MetricFilter.contains("foo").matches("bar.bar", mock(Metric.class)))
+ .isFalse();
+ }
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryListenerTest.java b/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryListenerTest.java
index 2d579ef..b7ef32b 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryListenerTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryListenerTest.java
@@ -3,10 +3,9 @@ package com.codahale.metrics;
import org.junit.Test;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
public class MetricRegistryListenerTest {
- private final Gauge gauge = mock(Gauge.class);
private final Counter counter = mock(Counter.class);
private final Histogram histogram = mock(Histogram.class);
private final Meter meter = mock(Meter.class);
@@ -16,42 +15,42 @@ public class MetricRegistryListenerTest {
};
@Test
- public void noOpsOnGaugeAdded() throws Exception {
- listener.onGaugeAdded("blah", gauge);
-
- verifyZeroInteractions(gauge);
+ public void noOpsOnGaugeAdded() {
+ listener.onGaugeAdded("blah", () -> {
+ throw new RuntimeException("Should not be called");
+ });
}
@Test
- public void noOpsOnCounterAdded() throws Exception {
+ public void noOpsOnCounterAdded() {
listener.onCounterAdded("blah", counter);
- verifyZeroInteractions(counter);
+ verifyNoInteractions(counter);
}
@Test
- public void noOpsOnHistogramAdded() throws Exception {
+ public void noOpsOnHistogramAdded() {
listener.onHistogramAdded("blah", histogram);
- verifyZeroInteractions(histogram);
+ verifyNoInteractions(histogram);
}
@Test
- public void noOpsOnMeterAdded() throws Exception {
+ public void noOpsOnMeterAdded() {
listener.onMeterAdded("blah", meter);
- verifyZeroInteractions(meter);
+ verifyNoInteractions(meter);
}
@Test
- public void noOpsOnTimerAdded() throws Exception {
+ public void noOpsOnTimerAdded() {
listener.onTimerAdded("blah", timer);
- verifyZeroInteractions(timer);
+ verifyNoInteractions(timer);
}
@Test
- public void doesNotExplodeWhenMetricsAreRemoved() throws Exception {
+ public void doesNotExplodeWhenMetricsAreRemoved() {
listener.onGaugeRemoved("blah");
listener.onCounterRemoved("blah");
listener.onHistogramRemoved("blah");
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 f314c19..cbc86ac 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryTest.java
@@ -1,33 +1,38 @@
package com.codahale.metrics;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
import static com.codahale.metrics.MetricRegistry.name;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
public class MetricRegistryTest {
private final MetricRegistryListener listener = mock(MetricRegistryListener.class);
private final MetricRegistry registry = new MetricRegistry();
- @SuppressWarnings("unchecked")
- private final Gauge<String> gauge = mock(Gauge.class);
+ private final Gauge<String> gauge = () -> "";
+ private final SettableGauge<String> settableGauge = new DefaultSettableGauge<>("");
private final Counter counter = mock(Counter.class);
private final Histogram histogram = mock(Histogram.class);
private final Meter meter = mock(Meter.class);
private final Timer timer = mock(Timer.class);
@Before
- public void setUp() throws Exception {
+ public void setUp() {
registry.addListener(listener);
}
@Test
- public void registeringAGaugeTriggersANotification() throws Exception {
+ public void registeringAGaugeTriggersANotification() {
assertThat(registry.register("thing", gauge))
.isEqualTo(gauge);
@@ -35,7 +40,7 @@ public class MetricRegistryTest {
}
@Test
- public void removingAGaugeTriggersANotification() throws Exception {
+ public void removingAGaugeTriggersANotification() {
registry.register("thing", gauge);
assertThat(registry.remove("thing"))
@@ -45,7 +50,7 @@ public class MetricRegistryTest {
}
@Test
- public void registeringACounterTriggersANotification() throws Exception {
+ public void registeringACounterTriggersANotification() {
assertThat(registry.register("thing", counter))
.isEqualTo(counter);
@@ -53,7 +58,7 @@ public class MetricRegistryTest {
}
@Test
- public void accessingACounterRegistersAndReusesTheCounter() throws Exception {
+ public void accessingACounterRegistersAndReusesTheCounter() {
final Counter counter1 = registry.counter("thing");
final Counter counter2 = registry.counter("thing");
@@ -64,13 +69,8 @@ public class MetricRegistryTest {
}
@Test
- public void accessingACustomCounterRegistersAndReusesTheCounter() throws Exception {
- final MetricRegistry.MetricSupplier<Counter> supplier = new MetricRegistry.MetricSupplier<Counter>() {
- @Override
- public Counter newMetric() {
- return counter;
- }
- };
+ public void accessingACustomCounterRegistersAndReusesTheCounter() {
+ final MetricRegistry.MetricSupplier<Counter> supplier = () -> counter;
final Counter counter1 = registry.counter("thing", supplier);
final Counter counter2 = registry.counter("thing", supplier);
@@ -82,7 +82,7 @@ public class MetricRegistryTest {
@Test
- public void removingACounterTriggersANotification() throws Exception {
+ public void removingACounterTriggersANotification() {
registry.register("thing", counter);
assertThat(registry.remove("thing"))
@@ -92,7 +92,7 @@ public class MetricRegistryTest {
}
@Test
- public void registeringAHistogramTriggersANotification() throws Exception {
+ public void registeringAHistogramTriggersANotification() {
assertThat(registry.register("thing", histogram))
.isEqualTo(histogram);
@@ -100,7 +100,7 @@ public class MetricRegistryTest {
}
@Test
- public void accessingAHistogramRegistersAndReusesIt() throws Exception {
+ public void accessingAHistogramRegistersAndReusesIt() {
final Histogram histogram1 = registry.histogram("thing");
final Histogram histogram2 = registry.histogram("thing");
@@ -111,13 +111,8 @@ public class MetricRegistryTest {
}
@Test
- public void accessingACustomHistogramRegistersAndReusesIt() throws Exception {
- final MetricRegistry.MetricSupplier<Histogram> supplier = new MetricRegistry.MetricSupplier<Histogram>() {
- @Override
- public Histogram newMetric() {
- return histogram;
- }
- };
+ public void accessingACustomHistogramRegistersAndReusesIt() {
+ final MetricRegistry.MetricSupplier<Histogram> supplier = () -> histogram;
final Histogram histogram1 = registry.histogram("thing", supplier);
final Histogram histogram2 = registry.histogram("thing", supplier);
@@ -128,7 +123,7 @@ public class MetricRegistryTest {
}
@Test
- public void removingAHistogramTriggersANotification() throws Exception {
+ public void removingAHistogramTriggersANotification() {
registry.register("thing", histogram);
assertThat(registry.remove("thing"))
@@ -138,7 +133,7 @@ public class MetricRegistryTest {
}
@Test
- public void registeringAMeterTriggersANotification() throws Exception {
+ public void registeringAMeterTriggersANotification() {
assertThat(registry.register("thing", meter))
.isEqualTo(meter);
@@ -146,7 +141,7 @@ public class MetricRegistryTest {
}
@Test
- public void accessingAMeterRegistersAndReusesIt() throws Exception {
+ public void accessingAMeterRegistersAndReusesIt() {
final Meter meter1 = registry.meter("thing");
final Meter meter2 = registry.meter("thing");
@@ -157,13 +152,8 @@ public class MetricRegistryTest {
}
@Test
- public void accessingACustomMeterRegistersAndReusesIt() throws Exception {
- final MetricRegistry.MetricSupplier<Meter> supplier = new MetricRegistry.MetricSupplier<Meter>() {
- @Override
- public Meter newMetric() {
- return meter;
- }
- };
+ public void accessingACustomMeterRegistersAndReusesIt() {
+ final MetricRegistry.MetricSupplier<Meter> supplier = () -> meter;
final Meter meter1 = registry.meter("thing", supplier);
final Meter meter2 = registry.meter("thing", supplier);
@@ -173,8 +163,8 @@ public class MetricRegistryTest {
verify(listener).onMeterAdded("thing", meter1);
}
- @Test
- public void removingAMeterTriggersANotification() throws Exception {
+ @Test
+ public void removingAMeterTriggersANotification() {
registry.register("thing", meter);
assertThat(registry.remove("thing"))
@@ -184,7 +174,7 @@ public class MetricRegistryTest {
}
@Test
- public void registeringATimerTriggersANotification() throws Exception {
+ public void registeringATimerTriggersANotification() {
assertThat(registry.register("thing", timer))
.isEqualTo(timer);
@@ -192,7 +182,7 @@ public class MetricRegistryTest {
}
@Test
- public void accessingATimerRegistersAndReusesIt() throws Exception {
+ public void accessingATimerRegistersAndReusesIt() {
final Timer timer1 = registry.timer("thing");
final Timer timer2 = registry.timer("thing");
@@ -203,13 +193,8 @@ public class MetricRegistryTest {
}
@Test
- public void accessingACustomTimerRegistersAndReusesIt() throws Exception {
- final MetricRegistry.MetricSupplier<Timer> supplier = new MetricRegistry.MetricSupplier<Timer>() {
- @Override
- public Timer newMetric() {
- return timer;
- }
- };
+ public void accessingACustomTimerRegistersAndReusesIt() {
+ final MetricRegistry.MetricSupplier<Timer> supplier = () -> timer;
final Timer timer1 = registry.timer("thing", supplier);
final Timer timer2 = registry.timer("thing", supplier);
@@ -221,7 +206,7 @@ public class MetricRegistryTest {
@Test
- public void removingATimerTriggersANotification() throws Exception {
+ public void removingATimerTriggersANotification() {
registry.register("thing", timer);
assertThat(registry.remove("thing"))
@@ -231,13 +216,43 @@ public class MetricRegistryTest {
}
@Test
- public void accessingACustomGaugeRegistersAndReusesIt() throws Exception {
- final MetricRegistry.MetricSupplier<Gauge> supplier = new MetricRegistry.MetricSupplier<Gauge>() {
- @Override
- public Gauge newMetric() {
- return gauge;
- }
- };
+ public void accessingASettableGaugeRegistersAndReusesIt() {
+ final SettableGauge<String> gauge1 = registry.gauge("thing");
+ gauge1.setValue("Test");
+ final Gauge<String> gauge2 = registry.gauge("thing");
+
+ assertThat(gauge1).isSameAs(gauge2);
+ assertThat(gauge2.getValue()).isEqualTo("Test");
+
+ verify(listener).onGaugeAdded("thing", gauge1);
+ }
+
+ @Test
+ public void accessingAnExistingGaugeReusesIt() {
+ final Gauge<String> gauge1 = registry.gauge("thing", () -> () -> "string-gauge");
+ final Gauge<String> gauge2 = registry.gauge("thing", () -> new DefaultSettableGauge<>("settable-gauge"));
+
+ assertThat(gauge1).isSameAs(gauge2);
+ assertThat(gauge2.getValue()).isEqualTo("string-gauge");
+
+ verify(listener).onGaugeAdded("thing", gauge1);
+ }
+
+ @Test
+ public void accessingAnExistingSettableGaugeReusesIt() {
+ final Gauge<String> gauge1 = registry.gauge("thing", () -> new DefaultSettableGauge<>("settable-gauge"));
+ final Gauge<String> gauge2 = registry.gauge("thing");
+
+ assertThat(gauge1).isSameAs(gauge2);
+ assertThat(gauge2.getValue()).isEqualTo("settable-gauge");
+
+ verify(listener).onGaugeAdded("thing", gauge1);
+ }
+
+ @Test
+ @SuppressWarnings("rawtypes")
+ public void accessingACustomGaugeRegistersAndReusesIt() {
+ final MetricRegistry.MetricSupplier<Gauge> supplier = () -> gauge;
final Gauge gauge1 = registry.gauge("thing", supplier);
final Gauge gauge2 = registry.gauge("thing", supplier);
@@ -247,9 +262,20 @@ public class MetricRegistryTest {
verify(listener).onGaugeAdded("thing", gauge1);
}
+ @Test
+ public void settableGaugeIsTreatedLikeAGauge() {
+ final MetricRegistry.MetricSupplier<SettableGauge<String>> supplier = () -> settableGauge;
+ final SettableGauge<String> gauge1 = registry.gauge("thing", supplier);
+ final SettableGauge<String> gauge2 = registry.gauge("thing", supplier);
+
+ assertThat(gauge1)
+ .isSameAs(gauge2);
+
+ verify(listener).onGaugeAdded("thing", gauge1);
+ }
@Test
- public void addingAListenerWithExistingMetricsCatchesItUp() throws Exception {
+ public void addingAListenerWithExistingMetricsCatchesItUp() {
registry.register("gauge", gauge);
registry.register("counter", counter);
registry.register("histogram", histogram);
@@ -267,7 +293,7 @@ public class MetricRegistryTest {
}
@Test
- public void aRemovedListenerDoesNotReceiveUpdates() throws Exception {
+ public void aRemovedListenerDoesNotReceiveUpdates() {
registry.register("gauge", gauge);
registry.removeListener(listener);
registry.register("gauge2", gauge);
@@ -276,7 +302,7 @@ public class MetricRegistryTest {
}
@Test
- public void hasAMapOfRegisteredGauges() throws Exception {
+ public void hasAMapOfRegisteredGauges() {
registry.register("gauge", gauge);
assertThat(registry.getGauges())
@@ -284,7 +310,7 @@ public class MetricRegistryTest {
}
@Test
- public void hasAMapOfRegisteredCounters() throws Exception {
+ public void hasAMapOfRegisteredCounters() {
registry.register("counter", counter);
assertThat(registry.getCounters())
@@ -292,7 +318,7 @@ public class MetricRegistryTest {
}
@Test
- public void hasAMapOfRegisteredHistograms() throws Exception {
+ public void hasAMapOfRegisteredHistograms() {
registry.register("histogram", histogram);
assertThat(registry.getHistograms())
@@ -300,7 +326,7 @@ public class MetricRegistryTest {
}
@Test
- public void hasAMapOfRegisteredMeters() throws Exception {
+ public void hasAMapOfRegisteredMeters() {
registry.register("meter", meter);
assertThat(registry.getMeters())
@@ -308,7 +334,7 @@ public class MetricRegistryTest {
}
@Test
- public void hasAMapOfRegisteredTimers() throws Exception {
+ public void hasAMapOfRegisteredTimers() {
registry.register("timer", timer);
assertThat(registry.getTimers())
@@ -316,7 +342,7 @@ public class MetricRegistryTest {
}
@Test
- public void hasASetOfRegisteredMetricNames() throws Exception {
+ public void hasASetOfRegisteredMetricNames() {
registry.register("gauge", gauge);
registry.register("counter", counter);
registry.register("histogram", histogram);
@@ -328,15 +354,12 @@ public class MetricRegistryTest {
}
@Test
- public void registersMultipleMetrics() throws Exception {
- final MetricSet metrics = new MetricSet() {
- @Override
- public Map<String, Metric> getMetrics() {
- final Map<String, Metric> metrics = new HashMap<String, Metric>();
- metrics.put("gauge", gauge);
- metrics.put("counter", counter);
- return metrics;
- }
+ public void registersMultipleMetrics() {
+ final MetricSet metrics = () -> {
+ final Map<String, Metric> m = new HashMap<>();
+ m.put("gauge", gauge);
+ m.put("counter", counter);
+ return m;
};
registry.registerAll(metrics);
@@ -346,15 +369,12 @@ public class MetricRegistryTest {
}
@Test
- public void registersMultipleMetricsWithAPrefix() throws Exception {
- final MetricSet metrics = new MetricSet() {
- @Override
- public Map<String, Metric> getMetrics() {
- final Map<String, Metric> metrics = new HashMap<String, Metric>();
- metrics.put("gauge", gauge);
- metrics.put("counter", counter);
- return metrics;
- }
+ public void registersMultipleMetricsWithAPrefix() {
+ final MetricSet metrics = () -> {
+ final Map<String, Metric> m = new HashMap<>();
+ m.put("gauge", gauge);
+ m.put("counter", counter);
+ return m;
};
registry.register("my", metrics);
@@ -364,24 +384,18 @@ public class MetricRegistryTest {
}
@Test
- public void registersRecursiveMetricSets() throws Exception {
- final MetricSet inner = new MetricSet() {
- @Override
- public Map<String, Metric> getMetrics() {
- final Map<String, Metric> metrics = new HashMap<String, Metric>();
- metrics.put("gauge", gauge);
- return metrics;
- }
+ public void registersRecursiveMetricSets() {
+ final MetricSet inner = () -> {
+ final Map<String, Metric> m = new HashMap<>();
+ m.put("gauge", gauge);
+ return m;
};
- final MetricSet outer = new MetricSet() {
- @Override
- public Map<String, Metric> getMetrics() {
- final Map<String, Metric> metrics = new HashMap<String, Metric>();
- metrics.put("inner", inner);
- metrics.put("counter", counter);
- return metrics;
- }
+ final MetricSet outer = () -> {
+ final Map<String, Metric> m = new HashMap<>();
+ m.put("inner", inner);
+ m.put("counter", counter);
+ return m;
};
registry.register("my", outer);
@@ -391,7 +405,7 @@ public class MetricRegistryTest {
}
@Test
- public void registersMetricsFromAnotherRegistry() throws Exception {
+ public void registersMetricsFromAnotherRegistry() {
MetricRegistry other = new MetricRegistry();
other.register("gauge", gauge);
registry.register("nested", other);
@@ -399,57 +413,52 @@ public class MetricRegistryTest {
}
@Test
- public void concatenatesStringsToFormADottedName() throws Exception {
+ public void concatenatesStringsToFormADottedName() {
assertThat(name("one", "two", "three"))
.isEqualTo("one.two.three");
}
@Test
@SuppressWarnings("NullArgumentToVariableArgMethod")
- public void elidesNullValuesFromNamesWhenOnlyOneNullPassedIn() throws Exception {
- assertThat(name("one", (String)null))
+ public void elidesNullValuesFromNamesWhenOnlyOneNullPassedIn() {
+ assertThat(name("one", (String) null))
.isEqualTo("one");
}
@Test
- public void elidesNullValuesFromNamesWhenManyNullsPassedIn() throws Exception {
+ public void elidesNullValuesFromNamesWhenManyNullsPassedIn() {
assertThat(name("one", null, null))
.isEqualTo("one");
}
@Test
- public void elidesNullValuesFromNamesWhenNullAndNotNullPassedIn() throws Exception {
+ public void elidesNullValuesFromNamesWhenNullAndNotNullPassedIn() {
assertThat(name("one", null, "three"))
.isEqualTo("one.three");
}
@Test
- public void elidesEmptyStringsFromNames() throws Exception {
+ public void elidesEmptyStringsFromNames() {
assertThat(name("one", "", "three"))
.isEqualTo("one.three");
}
@Test
- public void concatenatesClassNamesWithStringsToFormADottedName() throws Exception {
+ public void concatenatesClassNamesWithStringsToFormADottedName() {
assertThat(name(MetricRegistryTest.class, "one", "two"))
.isEqualTo("com.codahale.metrics.MetricRegistryTest.one.two");
}
@Test
- public void concatenatesClassesWithoutCanonicalNamesWithStrings() throws Exception {
- final Gauge<String> g = new Gauge<String>() {
- @Override
- public String getValue() {
- return null;
- }
- };
+ public void concatenatesClassesWithoutCanonicalNamesWithStrings() {
+ final Gauge<String> g = () -> null;
assertThat(name(g.getClass(), "one", "two"))
- .isEqualTo("com.codahale.metrics.MetricRegistryTest$10.one.two");
+ .matches("com\\.codahale\\.metrics\\.MetricRegistryTest.+?\\.one\\.two");
}
@Test
- public void removesMetricsMatchingAFilter() throws Exception {
+ public void removesMetricsMatchingAFilter() {
registry.timer("timer-1");
registry.timer("timer-2");
registry.histogram("histogram-1");
@@ -457,12 +466,7 @@ public class MetricRegistryTest {
assertThat(registry.getNames())
.contains("timer-1", "timer-2", "histogram-1");
- registry.removeMatching(new MetricFilter() {
- @Override
- public boolean matches(String name, Metric metric) {
- return name.endsWith("1");
- }
- });
+ registry.removeMatching((name, metric) -> name.endsWith("1"));
assertThat(registry.getNames())
.doesNotContain("timer-1", "histogram-1");
@@ -472,4 +476,168 @@ public class MetricRegistryTest {
verify(listener).onTimerRemoved("timer-1");
verify(listener).onHistogramRemoved("histogram-1");
}
+
+ @Test
+ public void addingChildMetricAfterRegister() {
+ MetricRegistry parent = new MetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+
+ child.counter("test-1");
+ parent.register("child", child);
+ child.counter("test-2");
+
+ Set<String> parentMetrics = parent.getMetrics().keySet();
+ Set<String> childMetrics = child.getMetrics().keySet();
+
+ assertThat(parentMetrics)
+ .isEqualTo(childMetrics.stream().map(m -> "child." + m).collect(Collectors.toSet()));
+ }
+
+ @Test
+ public void addingMultipleChildMetricsAfterRegister() {
+ MetricRegistry parent = new MetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+
+ child.counter("test-1");
+ child.counter("test-2");
+ parent.register("child", child);
+ child.counter("test-3");
+ child.counter("test-4");
+
+ Set<String> parentMetrics = parent.getMetrics().keySet();
+ Set<String> childMetrics = child.getMetrics().keySet();
+
+ assertThat(parentMetrics)
+ .isEqualTo(childMetrics.stream().map(m -> "child." + m).collect(Collectors.toSet()));
+ }
+
+ @Test
+ public void addingDeepChildMetricsAfterRegister() {
+ MetricRegistry parent = new MetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+ MetricRegistry deepChild = new MetricRegistry();
+
+ deepChild.counter("test-1");
+ child.register("deep-child", deepChild);
+ deepChild.counter("test-2");
+
+ child.counter("test-3");
+ parent.register("child", child);
+ child.counter("test-4");
+
+ deepChild.counter("test-5");
+
+ Set<String> parentMetrics = parent.getMetrics().keySet();
+ Set<String> childMetrics = child.getMetrics().keySet();
+ Set<String> deepChildMetrics = deepChild.getMetrics().keySet();
+
+ assertThat(parentMetrics)
+ .isEqualTo(childMetrics.stream().map(m -> "child." + m).collect(Collectors.toSet()));
+
+ assertThat(childMetrics)
+ .containsAll(deepChildMetrics.stream().map(m -> "deep-child." + m).collect(Collectors.toSet()));
+
+ assertThat(deepChildMetrics.size()).isEqualTo(3);
+ assertThat(childMetrics.size()).isEqualTo(5);
+ }
+
+ @Test
+ public void removingChildMetricAfterRegister() {
+ MetricRegistry parent = new MetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+
+ child.counter("test-1");
+ parent.register("child", child);
+ child.counter("test-2");
+
+ child.remove("test-1");
+
+ Set<String> parentMetrics = parent.getMetrics().keySet();
+ Set<String> childMetrics = child.getMetrics().keySet();
+
+ assertThat(parentMetrics)
+ .isEqualTo(childMetrics.stream().map(m -> "child." + m).collect(Collectors.toSet()));
+
+ assertThat(childMetrics).doesNotContain("test-1");
+ }
+
+ @Test
+ public void removingMultipleChildMetricsAfterRegister() {
+ MetricRegistry parent = new MetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+
+ child.counter("test-1");
+ child.counter("test-2");
+ parent.register("child", child);
+ child.counter("test-3");
+ child.counter("test-4");
+
+ child.remove("test-1");
+ child.remove("test-3");
+
+ Set<String> parentMetrics = parent.getMetrics().keySet();
+ Set<String> childMetrics = child.getMetrics().keySet();
+
+ assertThat(parentMetrics)
+ .isEqualTo(childMetrics.stream().map(m -> "child." + m).collect(Collectors.toSet()));
+
+ assertThat(childMetrics).doesNotContain("test-1", "test-3");
+ }
+
+ @Test
+ public void removingDeepChildMetricsAfterRegister() {
+ MetricRegistry parent = new MetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+ MetricRegistry deepChild = new MetricRegistry();
+
+ deepChild.counter("test-1");
+ child.register("deep-child", deepChild);
+ deepChild.counter("test-2");
+
+ child.counter("test-3");
+ parent.register("child", child);
+ child.counter("test-4");
+
+ deepChild.remove("test-2");
+
+ Set<String> parentMetrics = parent.getMetrics().keySet();
+ Set<String> childMetrics = child.getMetrics().keySet();
+ Set<String> deepChildMetrics = deepChild.getMetrics().keySet();
+
+ assertThat(parentMetrics)
+ .isEqualTo(childMetrics.stream().map(m -> "child." + m).collect(Collectors.toSet()));
+
+ assertThat(childMetrics)
+ .containsAll(deepChildMetrics.stream().map(m -> "deep-child." + m).collect(Collectors.toSet()));
+
+ assertThat(deepChildMetrics).doesNotContain("test-2");
+
+ assertThat(deepChildMetrics.size()).isEqualTo(1);
+ assertThat(childMetrics.size()).isEqualTo(3);
+ }
+
+ @Test
+ public void registerNullMetric() {
+ MetricRegistry registry = new MetricRegistry();
+ try {
+ registry.register("any_name", null);
+ Assert.fail("NullPointerException must be thrown !!!");
+ } catch (NullPointerException e) {
+ Assert.assertEquals("metric == null", e.getMessage());
+ }
+ }
+
+ @Test
+ public void infersGaugeType() {
+ Gauge<Long> gauge = registry.registerGauge("gauge", () -> 10_000_000_000L);
+
+ assertThat(gauge.getValue()).isEqualTo(10_000_000_000L);
+ }
+
+ @Test
+ public void registersGaugeAsLambda() {
+ registry.registerGauge("gauge", () -> 3.14);
+
+ assertThat(registry.gauge("gauge").getValue()).isEqualTo(3.14);
+ }
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/NoopMetricRegistryTest.java b/metrics-core/src/test/java/com/codahale/metrics/NoopMetricRegistryTest.java
new file mode 100644
index 0000000..700c2a0
--- /dev/null
+++ b/metrics-core/src/test/java/com/codahale/metrics/NoopMetricRegistryTest.java
@@ -0,0 +1,495 @@
+package com.codahale.metrics;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class NoopMetricRegistryTest {
+ private final MetricRegistryListener listener = mock(MetricRegistryListener.class);
+ private final NoopMetricRegistry registry = new NoopMetricRegistry();
+ private final Gauge<String> gauge = () -> "";
+ private final Counter counter = mock(Counter.class);
+ private final Histogram histogram = mock(Histogram.class);
+ private final Meter meter = mock(Meter.class);
+ private final Timer timer = mock(Timer.class);
+
+ @Before
+ public void setUp() {
+ registry.addListener(listener);
+ }
+
+ @Test
+ public void registeringAGaugeTriggersNoNotification() {
+ assertThat(registry.register("thing", gauge)).isEqualTo(gauge);
+
+ verify(listener, never()).onGaugeAdded("thing", gauge);
+ }
+
+ @Test
+ public void removingAGaugeTriggersNoNotification() {
+ registry.register("thing", gauge);
+
+ assertThat(registry.remove("thing")).isFalse();
+
+ verify(listener, never()).onGaugeRemoved("thing");
+ }
+
+ @Test
+ public void registeringACounterTriggersNoNotification() {
+ assertThat(registry.register("thing", counter)).isEqualTo(counter);
+
+ verify(listener, never()).onCounterAdded("thing", counter);
+ }
+
+ @Test
+ public void accessingACounterRegistersAndReusesTheCounter() {
+ final Counter counter1 = registry.counter("thing");
+ final Counter counter2 = registry.counter("thing");
+
+ assertThat(counter1).isExactlyInstanceOf(NoopMetricRegistry.NoopCounter.class);
+ assertThat(counter2).isExactlyInstanceOf(NoopMetricRegistry.NoopCounter.class);
+ assertThat(counter1).isSameAs(counter2);
+
+ verify(listener, never()).onCounterAdded("thing", counter1);
+ }
+
+ @Test
+ public void accessingACustomCounterRegistersAndReusesTheCounter() {
+ final MetricRegistry.MetricSupplier<Counter> supplier = () -> counter;
+ final Counter counter1 = registry.counter("thing", supplier);
+ final Counter counter2 = registry.counter("thing", supplier);
+
+ assertThat(counter1).isExactlyInstanceOf(NoopMetricRegistry.NoopCounter.class);
+ assertThat(counter2).isExactlyInstanceOf(NoopMetricRegistry.NoopCounter.class);
+ assertThat(counter1).isSameAs(counter2);
+
+ verify(listener, never()).onCounterAdded("thing", counter1);
+ }
+
+
+ @Test
+ public void removingACounterTriggersNoNotification() {
+ registry.register("thing", counter);
+
+ assertThat(registry.remove("thing")).isFalse();
+
+ verify(listener, never()).onCounterRemoved("thing");
+ }
+
+ @Test
+ public void registeringAHistogramTriggersNoNotification() {
+ assertThat(registry.register("thing", histogram)).isEqualTo(histogram);
+
+ verify(listener, never()).onHistogramAdded("thing", histogram);
+ }
+
+ @Test
+ public void accessingAHistogramRegistersAndReusesIt() {
+ final Histogram histogram1 = registry.histogram("thing");
+ final Histogram histogram2 = registry.histogram("thing");
+
+ assertThat(histogram1).isExactlyInstanceOf(NoopMetricRegistry.NoopHistogram.class);
+ assertThat(histogram2).isExactlyInstanceOf(NoopMetricRegistry.NoopHistogram.class);
+ assertThat(histogram1).isSameAs(histogram2);
+
+ verify(listener, never()).onHistogramAdded("thing", histogram1);
+ }
+
+ @Test
+ public void accessingACustomHistogramRegistersAndReusesIt() {
+ final MetricRegistry.MetricSupplier<Histogram> supplier = () -> histogram;
+ final Histogram histogram1 = registry.histogram("thing", supplier);
+ final Histogram histogram2 = registry.histogram("thing", supplier);
+
+ assertThat(histogram1).isExactlyInstanceOf(NoopMetricRegistry.NoopHistogram.class);
+ assertThat(histogram2).isExactlyInstanceOf(NoopMetricRegistry.NoopHistogram.class);
+ assertThat(histogram1).isSameAs(histogram2);
+
+ verify(listener, never()).onHistogramAdded("thing", histogram1);
+ }
+
+ @Test
+ public void removingAHistogramTriggersNoNotification() {
+ registry.register("thing", histogram);
+
+ assertThat(registry.remove("thing")).isFalse();
+
+ verify(listener, never()).onHistogramRemoved("thing");
+ }
+
+ @Test
+ public void registeringAMeterTriggersNoNotification() {
+ assertThat(registry.register("thing", meter)).isEqualTo(meter);
+
+ verify(listener, never()).onMeterAdded("thing", meter);
+ }
+
+ @Test
+ public void accessingAMeterRegistersAndReusesIt() {
+ final Meter meter1 = registry.meter("thing");
+ final Meter meter2 = registry.meter("thing");
+
+ assertThat(meter1).isExactlyInstanceOf(NoopMetricRegistry.NoopMeter.class);
+ assertThat(meter2).isExactlyInstanceOf(NoopMetricRegistry.NoopMeter.class);
+ assertThat(meter1).isSameAs(meter2);
+
+ verify(listener, never()).onMeterAdded("thing", meter1);
+ }
+
+ @Test
+ public void accessingACustomMeterRegistersAndReusesIt() {
+ final MetricRegistry.MetricSupplier<Meter> supplier = () -> meter;
+ final Meter meter1 = registry.meter("thing", supplier);
+ final Meter meter2 = registry.meter("thing", supplier);
+
+ assertThat(meter1).isExactlyInstanceOf(NoopMetricRegistry.NoopMeter.class);
+ assertThat(meter2).isExactlyInstanceOf(NoopMetricRegistry.NoopMeter.class);
+ assertThat(meter1).isSameAs(meter2);
+
+ verify(listener, never()).onMeterAdded("thing", meter1);
+ }
+
+ @Test
+ public void removingAMeterTriggersNoNotification() {
+ registry.register("thing", meter);
+
+ assertThat(registry.remove("thing")).isFalse();
+
+ verify(listener, never()).onMeterRemoved("thing");
+ }
+
+ @Test
+ public void registeringATimerTriggersNoNotification() {
+ assertThat(registry.register("thing", timer)).isEqualTo(timer);
+
+ verify(listener, never()).onTimerAdded("thing", timer);
+ }
+
+ @Test
+ public void accessingATimerRegistersAndReusesIt() {
+ final Timer timer1 = registry.timer("thing");
+ final Timer timer2 = registry.timer("thing");
+
+ assertThat(timer1).isExactlyInstanceOf(NoopMetricRegistry.NoopTimer.class);
+ assertThat(timer2).isExactlyInstanceOf(NoopMetricRegistry.NoopTimer.class);
+ assertThat(timer1).isSameAs(timer2);
+
+ verify(listener, never()).onTimerAdded("thing", timer1);
+ }
+
+ @Test
+ public void accessingACustomTimerRegistersAndReusesIt() {
+ final MetricRegistry.MetricSupplier<Timer> supplier = () -> timer;
+ final Timer timer1 = registry.timer("thing", supplier);
+ final Timer timer2 = registry.timer("thing", supplier);
+
+ assertThat(timer1).isExactlyInstanceOf(NoopMetricRegistry.NoopTimer.class);
+ assertThat(timer2).isExactlyInstanceOf(NoopMetricRegistry.NoopTimer.class);
+ assertThat(timer1).isSameAs(timer2);
+
+ verify(listener, never()).onTimerAdded("thing", timer1);
+ }
+
+
+ @Test
+ public void removingATimerTriggersNoNotification() {
+ registry.register("thing", timer);
+
+ assertThat(registry.remove("thing")).isFalse();
+
+ verify(listener, never()).onTimerRemoved("thing");
+ }
+
+ @Test
+ public void accessingAGaugeRegistersAndReusesIt() {
+ final Gauge<Void> gauge1 = registry.gauge("thing");
+ final Gauge<Void> gauge2 = registry.gauge("thing");
+
+ assertThat(gauge1).isExactlyInstanceOf(NoopMetricRegistry.NoopGauge.class);
+ assertThat(gauge2).isExactlyInstanceOf(NoopMetricRegistry.NoopGauge.class);
+ assertThat(gauge1).isSameAs(gauge2);
+
+ verify(listener, never()).onGaugeAdded("thing", gauge1);
+ }
+
+ @Test
+ @SuppressWarnings("rawtypes")
+ public void accessingACustomGaugeRegistersAndReusesIt() {
+ final MetricRegistry.MetricSupplier<Gauge> supplier = () -> gauge;
+ final Gauge gauge1 = registry.gauge("thing", supplier);
+ final Gauge gauge2 = registry.gauge("thing", supplier);
+
+ assertThat(gauge1).isExactlyInstanceOf(NoopMetricRegistry.NoopGauge.class);
+ assertThat(gauge2).isExactlyInstanceOf(NoopMetricRegistry.NoopGauge.class);
+ assertThat(gauge1).isSameAs(gauge2);
+
+ verify(listener, never()).onGaugeAdded("thing", gauge1);
+ }
+
+
+ @Test
+ public void addingAListenerWithExistingMetricsDoesNotNotify() {
+ registry.register("gauge", gauge);
+ registry.register("counter", counter);
+ registry.register("histogram", histogram);
+ registry.register("meter", meter);
+ registry.register("timer", timer);
+
+ final MetricRegistryListener other = mock(MetricRegistryListener.class);
+ registry.addListener(other);
+
+ verify(other, never()).onGaugeAdded("gauge", gauge);
+ verify(other, never()).onCounterAdded("counter", counter);
+ verify(other, never()).onHistogramAdded("histogram", histogram);
+ verify(other, never()).onMeterAdded("meter", meter);
+ verify(other, never()).onTimerAdded("timer", timer);
+ }
+
+ @Test
+ public void aRemovedListenerDoesNotReceiveUpdates() {
+ registry.register("gauge", gauge);
+ registry.removeListener(listener);
+ registry.register("gauge2", gauge);
+
+ verify(listener, never()).onGaugeAdded("gauge2", gauge);
+ }
+
+ @Test
+ public void hasAMapOfRegisteredGauges() {
+ registry.register("gauge", gauge);
+
+ assertThat(registry.getGauges()).isEmpty();
+ }
+
+ @Test
+ public void hasAMapOfRegisteredCounters() {
+ registry.register("counter", counter);
+
+ assertThat(registry.getCounters()).isEmpty();
+ }
+
+ @Test
+ public void hasAMapOfRegisteredHistograms() {
+ registry.register("histogram", histogram);
+
+ assertThat(registry.getHistograms()).isEmpty();
+ }
+
+ @Test
+ public void hasAMapOfRegisteredMeters() {
+ registry.register("meter", meter);
+
+ assertThat(registry.getMeters()).isEmpty();
+ }
+
+ @Test
+ public void hasAMapOfRegisteredTimers() {
+ registry.register("timer", timer);
+
+ assertThat(registry.getTimers()).isEmpty();
+ }
+
+ @Test
+ public void hasASetOfRegisteredMetricNames() {
+ registry.register("gauge", gauge);
+ registry.register("counter", counter);
+ registry.register("histogram", histogram);
+ registry.register("meter", meter);
+ registry.register("timer", timer);
+
+ assertThat(registry.getNames()).isEmpty();
+ }
+
+ @Test
+ public void doesNotRegisterMultipleMetrics() {
+ final MetricSet metrics = () -> {
+ final Map<String, Metric> m = new HashMap<>();
+ m.put("gauge", gauge);
+ m.put("counter", counter);
+ return m;
+ };
+
+ registry.registerAll(metrics);
+
+ assertThat(registry.getNames()).isEmpty();
+ }
+
+ @Test
+ public void doesNotRegisterMultipleMetricsWithAPrefix() {
+ final MetricSet metrics = () -> {
+ final Map<String, Metric> m = new HashMap<>();
+ m.put("gauge", gauge);
+ m.put("counter", counter);
+ return m;
+ };
+
+ registry.register("my", metrics);
+
+ assertThat(registry.getNames()).isEmpty();
+ }
+
+ @Test
+ public void doesNotRegisterRecursiveMetricSets() {
+ final MetricSet inner = () -> {
+ final Map<String, Metric> m = new HashMap<>();
+ m.put("gauge", gauge);
+ return m;
+ };
+
+ final MetricSet outer = () -> {
+ final Map<String, Metric> m = new HashMap<>();
+ m.put("inner", inner);
+ m.put("counter", counter);
+ return m;
+ };
+
+ registry.register("my", outer);
+
+ assertThat(registry.getNames()).isEmpty();
+ }
+
+ @Test
+ public void doesNotRegisterMetricsFromAnotherRegistry() {
+ MetricRegistry other = new MetricRegistry();
+ other.register("gauge", gauge);
+ registry.register("nested", other);
+ assertThat(registry.getNames()).isEmpty();
+ }
+
+ @Test
+ public void removesMetricsMatchingAFilter() {
+ registry.timer("timer-1");
+ registry.timer("timer-2");
+ registry.histogram("histogram-1");
+
+ assertThat(registry.getNames()).isEmpty();
+
+ registry.removeMatching((name, metric) -> name.endsWith("1"));
+
+ assertThat(registry.getNames()).isEmpty();
+
+ verify(listener, never()).onTimerRemoved("timer-1");
+ verify(listener, never()).onHistogramRemoved("histogram-1");
+ }
+
+ @Test
+ public void addingChildMetricAfterRegister() {
+ MetricRegistry parent = new NoopMetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+
+ child.counter("test-1");
+ parent.register("child", child);
+ child.counter("test-2");
+
+ assertThat(parent.getMetrics()).isEmpty();
+ }
+
+ @Test
+ public void addingMultipleChildMetricsAfterRegister() {
+ MetricRegistry parent = new NoopMetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+
+ child.counter("test-1");
+ child.counter("test-2");
+ parent.register("child", child);
+ child.counter("test-3");
+ child.counter("test-4");
+
+ assertThat(parent.getMetrics()).isEmpty();
+ }
+
+ @Test
+ public void addingDeepChildMetricsAfterRegister() {
+ MetricRegistry parent = new NoopMetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+ MetricRegistry deepChild = new MetricRegistry();
+
+ deepChild.counter("test-1");
+ child.register("deep-child", deepChild);
+ deepChild.counter("test-2");
+
+ child.counter("test-3");
+ parent.register("child", child);
+ child.counter("test-4");
+
+ deepChild.counter("test-5");
+
+ assertThat(parent.getMetrics()).isEmpty();
+ assertThat(deepChild.getMetrics()).hasSize(3);
+ assertThat(child.getMetrics()).hasSize(5);
+ }
+
+ @Test
+ public void removingChildMetricAfterRegister() {
+ MetricRegistry parent = new NoopMetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+
+ child.counter("test-1");
+ parent.register("child", child);
+ child.counter("test-2");
+
+ child.remove("test-1");
+
+ assertThat(parent.getMetrics()).isEmpty();
+ assertThat(child.getMetrics()).doesNotContainKey("test-1");
+ }
+
+ @Test
+ public void removingMultipleChildMetricsAfterRegister() {
+ MetricRegistry parent = new NoopMetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+
+ child.counter("test-1");
+ child.counter("test-2");
+ parent.register("child", child);
+ child.counter("test-3");
+ child.counter("test-4");
+
+ child.remove("test-1");
+ child.remove("test-3");
+
+ assertThat(parent.getMetrics()).isEmpty();
+ assertThat(child.getMetrics()).doesNotContainKeys("test-1", "test-3");
+ }
+
+ @Test
+ public void removingDeepChildMetricsAfterRegister() {
+ MetricRegistry parent = new NoopMetricRegistry();
+ MetricRegistry child = new MetricRegistry();
+ MetricRegistry deepChild = new MetricRegistry();
+
+ deepChild.counter("test-1");
+ child.register("deep-child", deepChild);
+ deepChild.counter("test-2");
+
+ child.counter("test-3");
+ parent.register("child", child);
+ child.counter("test-4");
+
+ deepChild.remove("test-2");
+
+ Set<String> childMetrics = child.getMetrics().keySet();
+ Set<String> deepChildMetrics = deepChild.getMetrics().keySet();
+
+ assertThat(parent.getMetrics()).isEmpty();
+ assertThat(deepChildMetrics).hasSize(1);
+ assertThat(childMetrics).hasSize(3);
+ }
+
+ @Test
+ public void registerNullMetric() {
+ MetricRegistry registry = new NoopMetricRegistry();
+ assertThatNullPointerException()
+ .isThrownBy(() -> registry.register("any_name", null))
+ .withMessage("metric == null");
+ }
+}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/RatioGaugeTest.java b/metrics-core/src/test/java/com/codahale/metrics/RatioGaugeTest.java
index 9a9ccdb..b48fe3c 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/RatioGaugeTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/RatioGaugeTest.java
@@ -6,7 +6,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class RatioGaugeTest {
@Test
- public void ratiosAreHumanReadable() throws Exception {
+ public void ratiosAreHumanReadable() {
final RatioGauge.Ratio ratio = RatioGauge.Ratio.of(100, 200);
assertThat(ratio.toString())
@@ -14,7 +14,7 @@ public class RatioGaugeTest {
}
@Test
- public void calculatesTheRatioOfTheNumeratorToTheDenominator() throws Exception {
+ public void calculatesTheRatioOfTheNumeratorToTheDenominator() {
final RatioGauge regular = new RatioGauge() {
@Override
protected Ratio getRatio() {
@@ -27,7 +27,7 @@ public class RatioGaugeTest {
}
@Test
- public void handlesDivideByZeroIssues() throws Exception {
+ public void handlesDivideByZeroIssues() {
final RatioGauge divByZero = new RatioGauge() {
@Override
protected Ratio getRatio() {
@@ -40,7 +40,7 @@ public class RatioGaugeTest {
}
@Test
- public void handlesInfiniteDenominators() throws Exception {
+ public void handlesInfiniteDenominators() {
final RatioGauge infinite = new RatioGauge() {
@Override
protected Ratio getRatio() {
@@ -53,7 +53,7 @@ public class RatioGaugeTest {
}
@Test
- public void handlesNaNDenominators() throws Exception {
+ public void handlesNaNDenominators() {
final RatioGauge nan = new RatioGauge() {
@Override
protected Ratio getRatio() {
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 ae6a483..cd53c3a 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ScheduledReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ScheduledReporterTest.java
@@ -1,21 +1,32 @@
package com.codahale.metrics;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.SortedMap;
import java.util.TreeMap;
-import java.util.concurrent.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
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.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
public class ScheduledReporterTest {
- private final Gauge gauge = mock(Gauge.class);
+ private final Gauge<String> gauge = () -> "";
private final Counter counter = mock(Counter.class);
private final Histogram histogram = mock(Histogram.class);
private final Meter meter = mock(Meter.class);
@@ -38,6 +49,7 @@ public class ScheduledReporterTest {
private final ScheduledReporter[] reporters = new ScheduledReporter[] {reporter, reporterWithCustomExecutor, reporterWithExternallyManagedExecutor};
@Before
+ @SuppressWarnings("unchecked")
public void setUp() throws Exception {
registry.register("gauge", gauge);
registry.register("counter", counter);
@@ -54,16 +66,29 @@ public class ScheduledReporterTest {
reporterWithNullExecutor.stop();
}
+ @Test
+ public void createWithNullMetricRegistry() {
+ ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+ DummyReporter r = null;
+ try {
+ r = new DummyReporter(null, "example", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS, executor);
+ Assert.fail("NullPointerException must be thrown !!!");
+ } catch (NullPointerException e) {
+ Assert.assertEquals("registry == null", e.getMessage());
+ } finally {
+ if (r != null) {
+ r.close();
+ }
+ }
+ }
+
@Test
public void pollsPeriodically() throws Exception {
- final CountDownLatch latch = new CountDownLatch(2);
- reporter.start(100, 100, TimeUnit.MILLISECONDS, new Runnable() {
- @Override
- public void run() {
- if (latch.getCount() > 0) {
- reporter.report();
- latch.countDown();
- }
+ CountDownLatch latch = new CountDownLatch(2);
+ reporter.start(100, 100, TimeUnit.MILLISECONDS, () -> {
+ if (latch.getCount() > 0) {
+ reporter.report();
+ latch.countDown();
}
});
latch.await(5, TimeUnit.SECONDS);
@@ -81,7 +106,7 @@ public class ScheduledReporterTest {
public void shouldUsePeriodAsInitialDelayIfNotSpecifiedOtherwise() throws Exception {
reporterWithCustomMockExecutor.start(200, TimeUnit.MILLISECONDS);
- verify(mockExecutor, times(1)).scheduleAtFixedRate(
+ verify(mockExecutor, times(1)).scheduleWithFixedDelay(
any(Runnable.class), eq(200L), eq(200L), eq(TimeUnit.MILLISECONDS)
);
}
@@ -90,21 +115,18 @@ public class ScheduledReporterTest {
public void shouldStartWithSpecifiedInitialDelay() throws Exception {
reporterWithCustomMockExecutor.start(350, 100, TimeUnit.MILLISECONDS);
- verify(mockExecutor).scheduleAtFixedRate(
+ verify(mockExecutor).scheduleWithFixedDelay(
any(Runnable.class), eq(350L), eq(100L), eq(TimeUnit.MILLISECONDS)
);
}
@Test
public void shouldAutoCreateExecutorWhenItNull() throws Exception {
- final CountDownLatch latch = new CountDownLatch(2);
- reporterWithNullExecutor.start(100, 100, TimeUnit.MILLISECONDS, new Runnable() {
- @Override
- public void run() {
- if (latch.getCount() > 0) {
- reporterWithNullExecutor.report();
- latch.countDown();
- }
+ CountDownLatch latch = new CountDownLatch(2);
+ reporterWithNullExecutor.start(100, 100, TimeUnit.MILLISECONDS, () -> {
+ if (latch.getCount() > 0) {
+ reporterWithNullExecutor.report();
+ latch.countDown();
}
});
latch.await(5, TimeUnit.SECONDS);
@@ -185,8 +207,53 @@ public class ScheduledReporterTest {
assertEquals(2.0E-5, reporter.convertDuration(20), 0.0);
}
+ @Test
+ public void shouldReportMetricsOnShutdown() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ reporterWithNullExecutor.start(0, 10, TimeUnit.SECONDS, () -> {
+ if (latch.getCount() > 0) {
+ reporterWithNullExecutor.report();
+ latch.countDown();
+ }
+ });
+ latch.await(5, TimeUnit.SECONDS);
+ reporterWithNullExecutor.stop();
+
+ verify(reporterWithNullExecutor, times(2)).report(
+ map("gauge", gauge),
+ map("counter", counter),
+ map("histogram", histogram),
+ map("meter", meter),
+ map("timer", timer)
+ );
+ }
+
+ @Test
+ public void shouldRescheduleAfterReportFinish() throws Exception {
+ // the first report is triggered at T + 0.1 seconds and takes 0.8 seconds
+ // after the first report finishes at T + 0.9 seconds the next report is scheduled to run at T + 1.4 seconds
+ reporter.start(100, 500, TimeUnit.MILLISECONDS, () -> {
+ reporter.report();
+ try {
+ Thread.sleep(800);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+
+ Thread.sleep(1_000);
+
+ verify(reporter, times(1)).report(
+ map("gauge", gauge),
+ map("counter", counter),
+ map("histogram", histogram),
+ map("meter", meter),
+ map("timer", timer)
+ );
+ }
+
private <T> SortedMap<String, T> map(String name, T value) {
- final SortedMap<String, T> map = new TreeMap<String, T>();
+ final SortedMap<String, T> map = new TreeMap<>();
map.put(name, value);
return map;
}
@@ -195,19 +262,20 @@ public class ScheduledReporterTest {
private AtomicInteger executionCount = new AtomicInteger();
- public DummyReporter(MetricRegistry registry, String name, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit) {
+ 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) {
+ 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) {
+ DummyReporter(MetricRegistry registry, String name, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit, ScheduledExecutorService executor, boolean shutdownExecutorOnStop) {
super(registry, name, filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
}
@Override
+ @SuppressWarnings("rawtypes")
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 e9637ac..2affff0 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/SharedMetricRegistriesTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/SharedMetricRegistriesTest.java
@@ -2,20 +2,25 @@ package com.codahale.metrics;
import org.junit.Before;
import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.atomic.AtomicReference;
public class SharedMetricRegistriesTest {
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
@Before
- public void setUp() throws Exception {
- SharedMetricRegistries.setDefaultRegistryName(new AtomicReference<String>());
+ public void setUp() {
+ SharedMetricRegistries.setDefaultRegistryName(new AtomicReference<>());
SharedMetricRegistries.clear();
}
@Test
- public void memorizesRegistriesByName() throws Exception {
+ public void memorizesRegistriesByName() {
final MetricRegistry one = SharedMetricRegistries.getOrCreate("one");
final MetricRegistry two = SharedMetricRegistries.getOrCreate("one");
@@ -24,7 +29,7 @@ public class SharedMetricRegistriesTest {
}
@Test
- public void hasASetOfNames() throws Exception {
+ public void hasASetOfNames() {
SharedMetricRegistries.getOrCreate("one");
assertThat(SharedMetricRegistries.names())
@@ -32,7 +37,7 @@ public class SharedMetricRegistriesTest {
}
@Test
- public void removesRegistries() throws Exception {
+ public void removesRegistries() {
final MetricRegistry one = SharedMetricRegistries.getOrCreate("one");
SharedMetricRegistries.remove("one");
@@ -45,7 +50,7 @@ public class SharedMetricRegistriesTest {
}
@Test
- public void clearsRegistries() throws Exception {
+ public void clearsRegistries() {
SharedMetricRegistries.getOrCreate("one");
SharedMetricRegistries.getOrCreate("two");
@@ -56,17 +61,14 @@ public class SharedMetricRegistriesTest {
}
@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.");
- }
+ public void errorsWhenDefaultUnset() {
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Default registry name has not been set.");
+ SharedMetricRegistries.getDefault();
}
@Test
- public void createsDefaultRegistries() throws Exception {
+ public void createsDefaultRegistries() {
final String defaultName = "default";
final MetricRegistry registry = SharedMetricRegistries.setDefault(defaultName);
assertThat(registry).isNotNull();
@@ -75,18 +77,15 @@ public class SharedMetricRegistriesTest {
}
@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.");
- }
+ public void errorsWhenDefaultAlreadySet() {
+ SharedMetricRegistries.setDefault("foobah");
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Default metric registry name is already set.");
+ SharedMetricRegistries.setDefault("borg");
}
@Test
- public void setsDefaultExistingRegistries() throws Exception {
+ public void setsDefaultExistingRegistries() {
final String defaultName = "default";
final MetricRegistry registry = new MetricRegistry();
assertThat(SharedMetricRegistries.setDefault(defaultName, registry)).isEqualTo(registry);
diff --git a/metrics-core/src/test/java/com/codahale/metrics/SimpleSettableGaugeTest.java b/metrics-core/src/test/java/com/codahale/metrics/SimpleSettableGaugeTest.java
new file mode 100644
index 0000000..70a0245
--- /dev/null
+++ b/metrics-core/src/test/java/com/codahale/metrics/SimpleSettableGaugeTest.java
@@ -0,0 +1,28 @@
+package com.codahale.metrics;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SimpleSettableGaugeTest {
+
+ @Test
+ public void defaultValue() {
+ DefaultSettableGauge<Integer> settable = new DefaultSettableGauge<>(1);
+
+ assertThat(settable.getValue()).isEqualTo(1);
+ }
+
+ @Test
+ public void setValueAndThenGetValue() {
+ DefaultSettableGauge<String> settable = new DefaultSettableGauge<>("default");
+
+ settable.setValue("first");
+ assertThat(settable.getValue())
+ .isEqualTo("first");
+
+ settable.setValue("second");
+ assertThat(settable.getValue())
+ .isEqualTo("second");
+ }
+}
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 c0575f4..b17f58c 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/Slf4jReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/Slf4jReporterTest.java
@@ -4,64 +4,114 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.Marker;
+import java.util.EnumSet;
+import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
-import static org.mockito.Mockito.*;
+import static com.codahale.metrics.MetricAttribute.COUNT;
+import static com.codahale.metrics.MetricAttribute.M1_RATE;
+import static com.codahale.metrics.MetricAttribute.MEAN_RATE;
+import static com.codahale.metrics.MetricAttribute.MIN;
+import static com.codahale.metrics.MetricAttribute.P50;
+import static com.codahale.metrics.MetricAttribute.P999;
+import static com.codahale.metrics.MetricAttribute.STDDEV;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
public class Slf4jReporterTest {
+
private final Logger logger = mock(Logger.class);
private final Marker marker = mock(Marker.class);
private final MetricRegistry registry = mock(MetricRegistry.class);
- private final Slf4jReporter infoReporter = Slf4jReporter.forRegistry(registry)
- .outputTo(logger)
- .markWith(marker)
- .prefixedWith("prefix")
- .convertRatesTo(TimeUnit.SECONDS)
- .convertDurationsTo(TimeUnit.MILLISECONDS)
- .withLoggingLevel(Slf4jReporter.LoggingLevel.INFO)
- .filter(MetricFilter.ALL)
- .build();
-
- private final Slf4jReporter errorReporter = Slf4jReporter.forRegistry(registry)
- .outputTo(logger)
- .markWith(marker)
- .convertRatesTo(TimeUnit.SECONDS)
- .convertDurationsTo(TimeUnit.MILLISECONDS)
- .withLoggingLevel(Slf4jReporter.LoggingLevel.ERROR)
- .filter(MetricFilter.ALL)
- .build();
- @Test
- public void reportsGaugeValuesAtError() throws Exception {
- when(logger.isErrorEnabled(marker)).thenReturn(true);
- errorReporter.report(map("gauge", gauge("value")),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ /**
+ * The set of disabled metric attributes to pass to the Slf4jReporter builder
+ * in the default factory methods of {@link #infoReporter}
+ * and {@link #errorReporter}.
+ *
+ * This value can be overridden by tests before calling the {@link #infoReporter}
+ * and {@link #errorReporter} factory methods.
+ */
+ private Set<MetricAttribute> disabledMetricAttributes = null;
+
+ private Slf4jReporter infoReporter() {
+ return Slf4jReporter.forRegistry(registry)
+ .outputTo(logger)
+ .markWith(marker)
+ .prefixedWith("prefix")
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .withLoggingLevel(Slf4jReporter.LoggingLevel.INFO)
+ .filter(MetricFilter.ALL)
+ .disabledMetricAttributes(disabledMetricAttributes)
+ .build();
+ }
- verify(logger).error(marker, "type={}, name={}, value={}", new Object[]{"GAUGE", "gauge", "value"});
+ private Slf4jReporter errorReporter() {
+ return Slf4jReporter.forRegistry(registry)
+ .outputTo(logger)
+ .markWith(marker)
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .withLoggingLevel(Slf4jReporter.LoggingLevel.ERROR)
+ .filter(MetricFilter.ALL)
+ .disabledMetricAttributes(disabledMetricAttributes)
+ .build();
}
@Test
- public void reportsCounterValuesAtError() throws Exception {
- final Counter counter = mock(Counter.class);
- when(counter.getCount()).thenReturn(100L);
+ public void reportsGaugeValuesAtErrorDefault() {
+ reportsGaugeValuesAtError();
+ }
+
+ @Test
+ public void reportsGaugeValuesAtErrorAllDisabled() {
+ disabledMetricAttributes = EnumSet.allOf(MetricAttribute.class); // has no effect
+ reportsGaugeValuesAtError();
+ }
+
+ private void reportsGaugeValuesAtError() {
when(logger.isErrorEnabled(marker)).thenReturn(true);
+ errorReporter().report(map("gauge", () -> "value"),
+ map(),
+ map(),
+ map(),
+ map());
- errorReporter.report(this.<Gauge>map(),
- map("test.counter", counter),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ verify(logger).error(marker, "type=GAUGE, name=gauge, value=value");
+ }
+
+
+ private Timer timer() {
+ 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));
- verify(logger).error(marker, "type={}, name={}, count={}", new Object[]{"COUNTER", "test.counter", 100L});
+ when(timer.getSnapshot()).thenReturn(snapshot);
+ return timer;
}
- @Test
- public void reportsHistogramValuesAtError() throws Exception {
+ private Histogram histogram() {
final Histogram histogram = mock(Histogram.class);
when(histogram.getCount()).thenReturn(1L);
@@ -78,282 +128,235 @@ public class Slf4jReporterTest {
when(snapshot.get999thPercentile()).thenReturn(11.0);
when(histogram.getSnapshot()).thenReturn(snapshot);
- when(logger.isErrorEnabled(marker)).thenReturn(true);
-
- errorReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- map("test.histogram", histogram),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(logger).error(marker,
- "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}",
- "HISTOGRAM",
- "test.histogram",
- 1L,
- 4L,
- 2L,
- 3.0,
- 5.0,
- 6.0,
- 7.0,
- 8.0,
- 9.0,
- 10.0,
- 11.0);
+ return histogram;
}
- @Test
- public void reportsMeterValuesAtError() throws Exception {
+ private Meter meter() {
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);
+ return meter;
+ }
+
+ private Counter counter() {
+ final Counter counter = mock(Counter.class);
+ when(counter.getCount()).thenReturn(100L);
+ return counter;
+ }
+
+ @Test
+ public void reportsCounterValuesAtErrorDefault() {
+ reportsCounterValuesAtError();
+ }
+
+ @Test
+ public void reportsCounterValuesAtErrorAllDisabled() {
+ disabledMetricAttributes = EnumSet.allOf(MetricAttribute.class); // has no effect
+ reportsCounterValuesAtError();
+ }
+
+ private void reportsCounterValuesAtError() {
+ final Counter counter = counter();
when(logger.isErrorEnabled(marker)).thenReturn(true);
- errorReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- map("test.meter", meter),
- this.<Timer>map());
-
- verify(logger).error(marker,
- "type={}, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
- "METER",
- "test.meter",
- 1L,
- 2.0,
- 3.0,
- 4.0,
- 5.0,
- "events/second");
+ errorReporter().report(map(),
+ map("test.counter", counter),
+ map(),
+ map(),
+ map());
+
+ verify(logger).error(marker, "type=COUNTER, name=test.counter, count=100");
}
@Test
- public void reportsTimerValuesAtError() throws Exception {
- final Timer timer = mock(Timer.class);
- when(timer.getCount()).thenReturn(1L);
+ public void reportsHistogramValuesAtErrorDefault() {
+ reportsHistogramValuesAtError("type=HISTOGRAM, name=test.histogram, count=1, min=4, " +
+ "max=2, mean=3.0, stddev=5.0, p50=6.0, p75=7.0, p95=8.0, p98=9.0, p99=10.0, p999=11.0");
+ }
- when(timer.getMeanRate()).thenReturn(2.0);
- when(timer.getOneMinuteRate()).thenReturn(3.0);
- when(timer.getFiveMinuteRate()).thenReturn(4.0);
- when(timer.getFifteenMinuteRate()).thenReturn(5.0);
+ @Test
+ public void reportsHistogramValuesAtErrorWithDisabledMetricAttributes() {
+ disabledMetricAttributes = EnumSet.of(COUNT, MIN, P50);
+ reportsHistogramValuesAtError("type=HISTOGRAM, name=test.histogram, max=2, mean=3.0, " +
+ "stddev=5.0, p75=7.0, p95=8.0, p98=9.0, p99=10.0, p999=11.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));
+ private void reportsHistogramValuesAtError(final String expectedLog) {
+ final Histogram histogram = histogram();
+ when(logger.isErrorEnabled(marker)).thenReturn(true);
- when(timer.getSnapshot()).thenReturn(snapshot);
+ errorReporter().report(map(),
+ map(),
+ map("test.histogram", histogram),
+ map(),
+ map());
+
+ verify(logger).error(marker, expectedLog);
+ }
+
+ @Test
+ public void reportsMeterValuesAtErrorDefault() {
+ reportsMeterValuesAtError("type=METER, name=test.meter, count=1, m1_rate=3.0, m5_rate=4.0, " +
+ "m15_rate=5.0, mean_rate=2.0, rate_unit=events/second");
+ }
+ @Test
+ public void reportsMeterValuesAtErrorWithDisabledMetricAttributes() {
+ disabledMetricAttributes = EnumSet.of(MIN, P50, M1_RATE);
+ reportsMeterValuesAtError("type=METER, name=test.meter, count=1, m5_rate=4.0, m15_rate=5.0, " +
+ "mean_rate=2.0, rate_unit=events/second");
+ }
+
+ private void reportsMeterValuesAtError(final String expectedLog) {
+ final Meter meter = meter();
when(logger.isErrorEnabled(marker)).thenReturn(true);
- errorReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
+ errorReporter().report(map(),
+ map(),
+ map(),
+ map("test.meter", meter),
+ map());
+
+ verify(logger).error(marker, expectedLog);
+ }
+
+
+ @Test
+ public void reportsTimerValuesAtErrorDefault() {
+ reportsTimerValuesAtError("type=TIMER, name=test.another.timer, count=1, min=300.0, max=100.0, " +
+ "mean=200.0, stddev=400.0, p50=500.0, p75=600.0, p95=700.0, p98=800.0, p99=900.0, p999=1000.0, " +
+ "m1_rate=3.0, m5_rate=4.0, m15_rate=5.0, mean_rate=2.0, rate_unit=events/second, " +
+ "duration_unit=milliseconds");
+ }
+
+ @Test
+ public void reportsTimerValuesAtErrorWithDisabledMetricAttributes() {
+ disabledMetricAttributes = EnumSet.of(MIN, STDDEV, P999, MEAN_RATE);
+ reportsTimerValuesAtError("type=TIMER, name=test.another.timer, count=1, max=100.0, mean=200.0, " +
+ "p50=500.0, p75=600.0, p95=700.0, p98=800.0, p99=900.0, m1_rate=3.0, m5_rate=4.0, m15_rate=5.0, " +
+ "rate_unit=events/second, duration_unit=milliseconds");
+ }
+
+ private void reportsTimerValuesAtError(final String expectedLog) {
+ final Timer timer = timer();
+
+ when(logger.isErrorEnabled(marker)).thenReturn(true);
+
+ errorReporter().report(map(),
+ map(),
+ map(),
+ map(),
map("test.another.timer", timer));
- verify(logger).error(marker,
- "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,
- 100.0,
- 200.0,
- 400.0,
- 500.0,
- 600.0,
- 700.0,
- 800.0,
- 900.0,
- 1000.0,
- 2.0,
- 3.0,
- 4.0,
- 5.0,
- "events/second",
- "milliseconds");
+ verify(logger).error(marker, expectedLog);
}
@Test
- public void reportsGaugeValues() throws Exception {
+ public void reportsGaugeValuesDefault() {
when(logger.isInfoEnabled(marker)).thenReturn(true);
- infoReporter.report(map("gauge", gauge("value")),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ infoReporter().report(map("gauge", () -> "value"),
+ map(),
+ map(),
+ map(),
+ map());
- verify(logger).info(marker, "type={}, name={}, value={}", new Object[]{"GAUGE", "prefix.gauge", "value"});
+ verify(logger).info(marker, "type=GAUGE, name=prefix.gauge, value=value");
}
+
@Test
- public void reportsCounterValues() throws Exception {
- final Counter counter = mock(Counter.class);
- when(counter.getCount()).thenReturn(100L);
+ public void reportsCounterValuesDefault() {
+ final Counter counter = counter();
when(logger.isInfoEnabled(marker)).thenReturn(true);
- infoReporter.report(this.<Gauge>map(),
+ infoReporter().report(map(),
map("test.counter", counter),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map());
- verify(logger).info(marker, "type={}, name={}, count={}", new Object[]{"COUNTER", "prefix.test.counter", 100L});
+ verify(logger).info(marker, "type=COUNTER, name=prefix.test.counter, count=100");
}
@Test
- public void reportsHistogramValues() throws Exception {
- 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);
+ public void reportsHistogramValuesDefault() {
+ final Histogram histogram = histogram();
when(logger.isInfoEnabled(marker)).thenReturn(true);
- infoReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
+ infoReporter().report(map(),
+ map(),
map("test.histogram", histogram),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(logger).info(marker,
- "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}",
- "HISTOGRAM",
- "prefix.test.histogram",
- 1L,
- 4L,
- 2L,
- 3.0,
- 5.0,
- 6.0,
- 7.0,
- 8.0,
- 9.0,
- 10.0,
- 11.0);
+ map(),
+ map());
+
+ verify(logger).info(marker, "type=HISTOGRAM, name=prefix.test.histogram, count=1, min=4, max=2, mean=3.0, " +
+ "stddev=5.0, p50=6.0, p75=7.0, p95=8.0, p98=9.0, p99=10.0, p999=11.0");
}
@Test
- public void reportsMeterValues() throws Exception {
- 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);
+ public void reportsMeterValuesDefault() {
+ final Meter meter = meter();
when(logger.isInfoEnabled(marker)).thenReturn(true);
- infoReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
+ infoReporter().report(map(),
+ map(),
+ map(),
map("test.meter", meter),
- this.<Timer>map());
-
- verify(logger).info(marker,
- "type={}, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
- "METER",
- "prefix.test.meter",
- 1L,
- 2.0,
- 3.0,
- 4.0,
- 5.0,
- "events/second");
+ map());
+
+ verify(logger).info(marker, "type=METER, name=prefix.test.meter, count=1, m1_rate=3.0, m5_rate=4.0, " +
+ "m15_rate=5.0, mean_rate=2.0, rate_unit=events/second");
}
@Test
- public void reportsTimerValues() throws Exception {
- final Timer timer = mock(Timer.class);
- when(timer.getCount()).thenReturn(1L);
+ public void reportsTimerValuesDefault() {
+ final Timer timer = timer();
+ when(logger.isInfoEnabled(marker)).thenReturn(true);
- when(timer.getMeanRate()).thenReturn(2.0);
- when(timer.getOneMinuteRate()).thenReturn(3.0);
- when(timer.getFiveMinuteRate()).thenReturn(4.0);
- when(timer.getFifteenMinuteRate()).thenReturn(5.0);
+ infoReporter().report(map(),
+ map(),
+ map(),
+ map(),
+ map("test.another.timer", timer));
- 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));
+ verify(logger).info(marker, "type=TIMER, name=prefix.test.another.timer, count=1, min=300.0, max=100.0, " +
+ "mean=200.0, stddev=400.0, p50=500.0, p75=600.0, p95=700.0, p98=800.0, p99=900.0, p999=1000.0," +
+ " m1_rate=3.0, m5_rate=4.0, m15_rate=5.0, mean_rate=2.0, rate_unit=events/second, duration_unit=milliseconds");
+ }
- when(timer.getSnapshot()).thenReturn(snapshot);
- when(logger.isInfoEnabled(marker)).thenReturn(true);
- infoReporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- map("test.another.timer", timer));
+ @Test
+ public void reportsAllMetricsDefault() {
+ when(logger.isInfoEnabled(marker)).thenReturn(true);
- verify(logger).info(marker,
- "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,
- 100.0,
- 200.0,
- 400.0,
- 500.0,
- 600.0,
- 700.0,
- 800.0,
- 900.0,
- 1000.0,
- 2.0,
- 3.0,
- 4.0,
- 5.0,
- "events/second",
- "milliseconds");
+ infoReporter().report(map("test.gauge", () -> "value"),
+ map("test.counter", counter()),
+ map("test.histogram", histogram()),
+ map("test.meter", meter()),
+ map("test.timer", timer()));
+
+ verify(logger).info(marker, "type=GAUGE, name=prefix.test.gauge, value=value");
+ verify(logger).info(marker, "type=COUNTER, name=prefix.test.counter, count=100");
+ verify(logger).info(marker, "type=HISTOGRAM, name=prefix.test.histogram, count=1, min=4, max=2, mean=3.0, " +
+ "stddev=5.0, p50=6.0, p75=7.0, p95=8.0, p98=9.0, p99=10.0, p999=11.0");
+ verify(logger).info(marker, "type=METER, name=prefix.test.meter, count=1, m1_rate=3.0, m5_rate=4.0, " +
+ "m15_rate=5.0, mean_rate=2.0, rate_unit=events/second");
+ verify(logger).info(marker, "type=TIMER, name=prefix.test.timer, count=1, min=300.0, max=100.0, " +
+ "mean=200.0, stddev=400.0, p50=500.0, p75=600.0, p95=700.0, p98=800.0, p99=900.0, p999=1000.0," +
+ " m1_rate=3.0, m5_rate=4.0, m15_rate=5.0, mean_rate=2.0, rate_unit=events/second, duration_unit=milliseconds");
}
private <T> SortedMap<String, T> map() {
- return new TreeMap<String, T>();
+ return new TreeMap<>();
}
private <T> SortedMap<String, T> map(String name, T metric) {
- final TreeMap<String, T> map = new TreeMap<String, T>();
+ final TreeMap<String, T> map = new TreeMap<>();
map.put(name, metric);
return map;
}
- private <T> Gauge gauge(T value) {
- final Gauge gauge = mock(Gauge.class);
- when(gauge.getValue()).thenReturn(value);
- return gauge;
- }
-
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTest.java b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTest.java
index 9636823..a9dec13 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTest.java
@@ -9,14 +9,14 @@ import org.junit.Test;
import java.util.Arrays;
import java.util.Random;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
@SuppressWarnings("Duplicates")
public class SlidingTimeWindowArrayReservoirTest {
@Test
- public void storesMeasurementsWithDuplicateTicks() throws Exception {
+ public void storesMeasurementsWithDuplicateTicks() {
final Clock clock = mock(Clock.class);
final SlidingTimeWindowArrayReservoir reservoir = new SlidingTimeWindowArrayReservoir(10, NANOSECONDS, clock);
@@ -26,11 +26,11 @@ public class SlidingTimeWindowArrayReservoirTest {
reservoir.update(2);
assertThat(reservoir.getSnapshot().getValues())
- .containsOnly(1, 2);
+ .containsOnly(1, 2);
}
@Test
- public void boundsMeasurementsToATimeWindow() throws Exception {
+ public void boundsMeasurementsToATimeWindow() {
final Clock clock = mock(Clock.class);
final SlidingTimeWindowArrayReservoir reservoir = new SlidingTimeWindowArrayReservoir(10, NANOSECONDS, clock);
@@ -50,7 +50,7 @@ public class SlidingTimeWindowArrayReservoirTest {
reservoir.update(5);
assertThat(reservoir.getSnapshot().getValues())
- .containsOnly(4, 5);
+ .containsOnly(4, 5);
}
@Test
@@ -111,8 +111,8 @@ public class SlidingTimeWindowArrayReservoirTest {
// 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);
+ .as("Bad reservoir size with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
+ .isLessThanOrEqualTo(window * 256);
}
// Update the clock
@@ -128,8 +128,8 @@ public class SlidingTimeWindowArrayReservoirTest {
// Check the final reservoir size
assertThat(reservoir.size())
- .as("Bad final reservoir size with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
- .isLessThanOrEqualTo(window * 256);
+ .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
@@ -142,8 +142,8 @@ public class SlidingTimeWindowArrayReservoirTest {
// The reservoir should now be empty
assertThat(reservoir.size())
- .as("Bad reservoir size after delay with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
- .isEqualTo(0);
+ .as("Bad reservoir size after delay with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
+ .isEqualTo(0);
}
}
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowMovingAveragesTest.java b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowMovingAveragesTest.java
new file mode 100644
index 0000000..878b36f
--- /dev/null
+++ b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowMovingAveragesTest.java
@@ -0,0 +1,166 @@
+package com.codahale.metrics;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Instant;
+
+import static com.codahale.metrics.SlidingTimeWindowMovingAverages.NUMBER_OF_BUCKETS;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SlidingTimeWindowMovingAveragesTest {
+
+ private ManualClock clock;
+ private SlidingTimeWindowMovingAverages movingAverages;
+ private Meter meter;
+
+ @Before
+ public void init() {
+ clock = new ManualClock();
+ movingAverages = new SlidingTimeWindowMovingAverages(clock);
+ meter = new Meter(movingAverages, clock);
+ }
+
+ @Test
+ public void normalizeIndex() {
+
+ SlidingTimeWindowMovingAverages stwm = new SlidingTimeWindowMovingAverages();
+
+ assertThat(stwm.normalizeIndex(0)).isEqualTo(0);
+ assertThat(stwm.normalizeIndex(900)).isEqualTo(0);
+ assertThat(stwm.normalizeIndex(9000)).isEqualTo(0);
+ assertThat(stwm.normalizeIndex(-900)).isEqualTo(0);
+
+ assertThat(stwm.normalizeIndex(1)).isEqualTo(1);
+
+ assertThat(stwm.normalizeIndex(899)).isEqualTo(899);
+ assertThat(stwm.normalizeIndex(-1)).isEqualTo(899);
+ assertThat(stwm.normalizeIndex(-901)).isEqualTo(899);
+ }
+
+ @Test
+ public void calculateIndexOfTick() {
+
+ SlidingTimeWindowMovingAverages stwm = new SlidingTimeWindowMovingAverages(clock);
+
+ assertThat(stwm.calculateIndexOfTick(Instant.ofEpochSecond(0L))).isEqualTo(0);
+ assertThat(stwm.calculateIndexOfTick(Instant.ofEpochSecond(1L))).isEqualTo(1);
+ }
+
+ @Test
+ public void mark_max_without_cleanup() {
+
+ int markCount = NUMBER_OF_BUCKETS;
+
+ // compensate the first addSeconds in the loop; first tick should be at zero
+ clock.addSeconds(-1);
+
+ for (int i = 0; i < markCount; i++) {
+ clock.addSeconds(1);
+ meter.mark();
+ }
+
+ // verify that no cleanup happened yet
+ assertThat(movingAverages.oldestBucketTime).isEqualTo(Instant.ofEpochSecond(0L));
+
+ assertThat(meter.getOneMinuteRate()).isEqualTo(60.0);
+ assertThat(meter.getFiveMinuteRate()).isEqualTo(300.0);
+ assertThat(meter.getFifteenMinuteRate()).isEqualTo(900.0);
+ }
+
+ @Test
+ public void mark_first_cleanup() {
+
+ int markCount = NUMBER_OF_BUCKETS + 1;
+
+ // compensate the first addSeconds in the loop; first tick should be at zero
+ clock.addSeconds(-1);
+
+ for (int i = 0; i < markCount; i++) {
+ clock.addSeconds(1);
+ meter.mark();
+ }
+
+ // verify that at least one cleanup happened
+ assertThat(movingAverages.oldestBucketTime).isNotEqualTo(Instant.EPOCH);
+
+ assertThat(meter.getOneMinuteRate()).isEqualTo(60.0);
+ assertThat(meter.getFiveMinuteRate()).isEqualTo(300.0);
+ assertThat(meter.getFifteenMinuteRate()).isEqualTo(900.0);
+ }
+
+ @Test
+ public void mark_10_values() {
+
+ // compensate the first addSeconds in the loop; first tick should be at zero
+ clock.addSeconds(-1);
+
+ for (int i = 0; i < 10; i++) {
+ clock.addSeconds(1);
+ meter.mark();
+ }
+
+ assertThat(meter.getCount()).isEqualTo(10L);
+ assertThat(meter.getOneMinuteRate()).isEqualTo(10.0);
+ assertThat(meter.getFiveMinuteRate()).isEqualTo(10.0);
+ assertThat(meter.getFifteenMinuteRate()).isEqualTo(10.0);
+ }
+
+ @Test
+ public void mark_1000_values() {
+
+ for (int i = 0; i < 1000; i++) {
+ clock.addSeconds(1);
+ meter.mark();
+ }
+
+ // only 60/300/900 of the 1000 events took place in the last 1/5/15 minute(s)
+ assertThat(meter.getOneMinuteRate()).isEqualTo(60.0);
+ assertThat(meter.getFiveMinuteRate()).isEqualTo(300.0);
+ assertThat(meter.getFifteenMinuteRate()).isEqualTo(900.0);
+ }
+
+ @Test
+ public void cleanup_pause_shorter_than_window() {
+
+ meter.mark(10);
+
+ // no mark for three minutes
+ clock.addSeconds(180);
+ assertThat(meter.getOneMinuteRate()).isEqualTo(0.0);
+ assertThat(meter.getFiveMinuteRate()).isEqualTo(10.0);
+ assertThat(meter.getFifteenMinuteRate()).isEqualTo(10.0);
+ }
+
+ @Test
+ public void cleanup_window_wrap_around() {
+
+ // mark at 14:40 minutes of the 15 minute window...
+ clock.addSeconds(880);
+ meter.mark(10);
+
+ // and query at 15:30 minutes (the bucket index must have wrapped around)
+ clock.addSeconds(50);
+ assertThat(meter.getOneMinuteRate()).isEqualTo(10.0);
+ assertThat(meter.getFiveMinuteRate()).isEqualTo(10.0);
+ assertThat(meter.getFifteenMinuteRate()).isEqualTo(10.0);
+
+ // and query at 30:10 minutes (the bucket index must have wrapped around for the second time)
+ clock.addSeconds(880);
+ assertThat(meter.getOneMinuteRate()).isEqualTo(0.0);
+ assertThat(meter.getFiveMinuteRate()).isEqualTo(0.0);
+ assertThat(meter.getFifteenMinuteRate()).isEqualTo(0.0);
+ }
+
+ @Test
+ public void cleanup_pause_longer_than_two_windows() {
+
+ meter.mark(10);
+
+ // after forty minutes all rates should be zero
+ clock.addSeconds(2400);
+ assertThat(meter.getOneMinuteRate()).isEqualTo(0.0);
+ assertThat(meter.getFiveMinuteRate()).isEqualTo(0.0);
+ assertThat(meter.getFifteenMinuteRate()).isEqualTo(0.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 d4a0a37..9b8458c 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowReservoirTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowReservoirTest.java
@@ -4,7 +4,6 @@ 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;
@@ -13,7 +12,7 @@ import static org.mockito.Mockito.when;
public class SlidingTimeWindowReservoirTest {
@Test
- public void storesMeasurementsWithDuplicateTicks() throws Exception {
+ public void storesMeasurementsWithDuplicateTicks() {
final Clock clock = mock(Clock.class);
final SlidingTimeWindowReservoir reservoir = new SlidingTimeWindowReservoir(10, NANOSECONDS, clock);
@@ -27,8 +26,10 @@ public class SlidingTimeWindowReservoirTest {
}
@Test
- public void boundsMeasurementsToATimeWindow() throws Exception {
+ public void boundsMeasurementsToATimeWindow() {
final Clock clock = mock(Clock.class);
+ when(clock.getTick()).thenReturn(0L);
+
final SlidingTimeWindowReservoir reservoir = new SlidingTimeWindowReservoir(10, NANOSECONDS, clock);
when(clock.getTick()).thenReturn(0L);
@@ -51,7 +52,7 @@ public class SlidingTimeWindowReservoirTest {
}
@Test
- public void testGetTickOverflow () {
+ public void testGetTickOverflow() {
final Random random = new Random(0);
final int window = 128;
@@ -62,14 +63,15 @@ public class SlidingTimeWindowReservoirTest {
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);
+ // Set the clock to overflow in (2*window+1)ns
+ clock.addNanos(Long.MAX_VALUE / 256 - 2 * window - clock.getTick());
+ assertThat(clock.getTick() * 256).isGreaterThan(0);
+
int updatesAfterThreshold = 0;
while (true) {
// Update the reservoir
diff --git a/metrics-core/src/test/java/com/codahale/metrics/SlidingWindowReservoirTest.java b/metrics-core/src/test/java/com/codahale/metrics/SlidingWindowReservoirTest.java
index d8d827c..322431e 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/SlidingWindowReservoirTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/SlidingWindowReservoirTest.java
@@ -8,7 +8,7 @@ public class SlidingWindowReservoirTest {
private final SlidingWindowReservoir reservoir = new SlidingWindowReservoir(3);
@Test
- public void handlesSmallDataStreams() throws Exception {
+ public void handlesSmallDataStreams() {
reservoir.update(1);
reservoir.update(2);
@@ -17,7 +17,7 @@ public class SlidingWindowReservoirTest {
}
@Test
- public void onlyKeepsTheMostRecentFromBigDataStreams() throws Exception {
+ public void onlyKeepsTheMostRecentFromBigDataStreams() {
reservoir.update(1);
reservoir.update(2);
reservoir.update(3);
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 c321cf6..94e14c5 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/TimerTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/TimerTest.java
@@ -2,13 +2,16 @@ package com.codahale.metrics;
import org.junit.Test;
-import java.util.concurrent.Callable;
+import java.time.Duration;
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;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
public class TimerTest {
private final Reservoir reservoir = mock(Reservoir.class);
@@ -24,7 +27,7 @@ public class TimerTest {
private final Timer timer = new Timer(reservoir, clock);
@Test
- public void hasRates() throws Exception {
+ public void hasRates() {
assertThat(timer.getCount())
.isZero();
@@ -42,7 +45,7 @@ public class TimerTest {
}
@Test
- public void updatesTheCountOnUpdates() throws Exception {
+ public void updatesTheCountOnUpdates() {
assertThat(timer.getCount())
.isZero();
@@ -54,12 +57,7 @@ public class TimerTest {
@Test
public void timesCallableInstances() throws Exception {
- final String value = timer.time(new Callable<String>() {
- @Override
- public String call() throws Exception {
- return "one";
- }
- });
+ final String value = timer.time(() -> "one");
assertThat(timer.getCount())
.isEqualTo(1);
@@ -71,14 +69,22 @@ public class TimerTest {
}
@Test
- public void timesRunnableInstances() throws Exception {
+ public void timesSuppliedInstances() {
+ final String value = timer.timeSupplier(() -> "one");
+
+ assertThat(timer.getCount())
+ .isEqualTo(1);
+
+ assertThat(value)
+ .isEqualTo("one");
+
+ verify(reservoir).update(50000000);
+ }
+
+ @Test
+ public void timesRunnableInstances() {
final AtomicBoolean called = new AtomicBoolean();
- timer.time(new Runnable() {
- @Override
- public void run() {
- called.set(true);
- }
- });
+ timer.time(() -> called.set(true));
assertThat(timer.getCount())
.isEqualTo(1);
@@ -90,7 +96,7 @@ public class TimerTest {
}
@Test
- public void timesContexts() throws Exception {
+ public void timesContexts() {
timer.time().stop();
assertThat(timer.getCount())
@@ -100,7 +106,7 @@ public class TimerTest {
}
@Test
- public void returnsTheSnapshotFromTheReservoir() throws Exception {
+ public void returnsTheSnapshotFromTheReservoir() {
final Snapshot snapshot = mock(Snapshot.class);
when(reservoir.getSnapshot()).thenReturn(snapshot);
@@ -109,12 +115,47 @@ public class TimerTest {
}
@Test
- public void ignoresNegativeValues() throws Exception {
+ public void ignoresNegativeValues() {
timer.update(-1, TimeUnit.SECONDS);
assertThat(timer.getCount())
.isZero();
- verifyZeroInteractions(reservoir);
+ verifyNoInteractions(reservoir);
+ }
+
+ @Test
+ public void java8Duration() {
+ timer.update(Duration.ofSeconds(1234));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+
+ verify(reservoir).update((long) 1234e9);
}
+
+ @Test
+ public void java8NegativeDuration() {
+ timer.update(Duration.ofMillis(-5678));
+
+ assertThat(timer.getCount()).isZero();
+
+ verifyNoInteractions(reservoir);
+ }
+
+ @Test
+ public void tryWithResourcesWork() {
+ assertThat(timer.getCount()).isZero();
+
+ int dummy = 0;
+ try (Timer.Context context = timer.time()) {
+ assertThat(context).isNotNull();
+ dummy += 1;
+ }
+ assertThat(dummy).isEqualTo(1);
+ assertThat(timer.getCount())
+ .isEqualTo(1);
+
+ verify(reservoir).update(50000000);
+ }
+
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/UniformReservoirTest.java b/metrics-core/src/test/java/com/codahale/metrics/UniformReservoirTest.java
index 2234578..6c90808 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/UniformReservoirTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/UniformReservoirTest.java
@@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class UniformReservoirTest {
@Test
@SuppressWarnings("unchecked")
- public void aReservoirOf100OutOf1000Elements() throws Exception {
+ public void aReservoirOf100OutOf1000Elements() {
final UniformReservoir reservoir = new UniformReservoir(100);
for (int i = 0; i < 1000; i++) {
reservoir.update(i);
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 46dcc88..d1ed091 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/UniformSnapshotTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/UniformSnapshotTest.java
@@ -11,83 +11,81 @@ import java.util.concurrent.TimeUnit;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.offset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
public class UniformSnapshotTest {
private final Snapshot snapshot = new UniformSnapshot(new long[]{5, 1, 2, 3, 4});
@Test
- public void smallQuantilesAreTheFirstValue() throws Exception {
+ public void smallQuantilesAreTheFirstValue() {
assertThat(snapshot.getValue(0.0))
.isEqualTo(1, offset(0.1));
}
@Test
- public void bigQuantilesAreTheLastValue() throws Exception {
+ public void bigQuantilesAreTheLastValue() {
assertThat(snapshot.getValue(1.0))
.isEqualTo(5, offset(0.1));
}
@Test(expected = IllegalArgumentException.class)
public void disallowsNotANumberQuantile() {
- snapshot.getValue( Double.NaN );
+ snapshot.getValue(Double.NaN);
}
@Test(expected = IllegalArgumentException.class)
public void disallowsNegativeQuantile() {
- snapshot.getValue( -0.5 );
+ snapshot.getValue(-0.5);
}
@Test(expected = IllegalArgumentException.class)
public void disallowsQuantileOverOne() {
- snapshot.getValue( 1.5 );
+ snapshot.getValue(1.5);
}
@Test
- public void hasAMedian() throws Exception {
+ public void hasAMedian() {
assertThat(snapshot.getMedian()).isEqualTo(3, offset(0.1));
}
@Test
- public void hasAp75() throws Exception {
+ public void hasAp75() {
assertThat(snapshot.get75thPercentile()).isEqualTo(4.5, offset(0.1));
}
@Test
- public void hasAp95() throws Exception {
+ public void hasAp95() {
assertThat(snapshot.get95thPercentile()).isEqualTo(5.0, offset(0.1));
}
@Test
- public void hasAp98() throws Exception {
+ public void hasAp98() {
assertThat(snapshot.get98thPercentile()).isEqualTo(5.0, offset(0.1));
}
@Test
- public void hasAp99() throws Exception {
+ public void hasAp99() {
assertThat(snapshot.get99thPercentile()).isEqualTo(5.0, offset(0.1));
}
@Test
- public void hasAp999() throws Exception {
+ public void hasAp999() {
assertThat(snapshot.get999thPercentile()).isEqualTo(5.0, offset(0.1));
}
@Test
- public void hasValues() throws Exception {
+ public void hasValues() {
assertThat(snapshot.getValues())
.containsOnly(1, 2, 3, 4, 5);
}
@Test
- public void hasASize() throws Exception {
+ public void hasASize() {
assertThat(snapshot.size())
.isEqualTo(5);
}
@Test
- public void canAlsoBeCreatedFromACollectionOfLongs() throws Exception {
+ public void canAlsoBeCreatedFromACollectionOfLongs() {
final Snapshot other = new UniformSnapshot(asList(5L, 1L, 2L, 3L, 4L));
assertThat(other.getValues())
@@ -96,21 +94,18 @@ public class UniformSnapshotTest {
@Test
public void correctlyCreatedFromCollectionWithWeakIterator() throws Exception {
- final ConcurrentSkipListSet<Long> values = new ConcurrentSkipListSet<Long>();
+ final ConcurrentSkipListSet<Long> values = new ConcurrentSkipListSet<>();
// 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();
- }
+ final Thread backgroundThread = new Thread(() -> {
+ 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();
@@ -128,7 +123,7 @@ public class UniformSnapshotTest {
}
@Test
- public void dumpsToAStream() throws Exception {
+ public void dumpsToAStream() {
final ByteArrayOutputStream output = new ByteArrayOutputStream();
snapshot.dump(output);
@@ -138,64 +133,64 @@ public class UniformSnapshotTest {
}
@Test
- public void calculatesTheMinimumValue() throws Exception {
+ public void calculatesTheMinimumValue() {
assertThat(snapshot.getMin())
.isEqualTo(1);
}
@Test
- public void calculatesTheMaximumValue() throws Exception {
+ public void calculatesTheMaximumValue() {
assertThat(snapshot.getMax())
.isEqualTo(5);
}
@Test
- public void calculatesTheMeanValue() throws Exception {
+ public void calculatesTheMeanValue() {
assertThat(snapshot.getMean())
.isEqualTo(3.0);
}
@Test
- public void calculatesTheStdDev() throws Exception {
+ public void calculatesTheStdDev() {
assertThat(snapshot.getStdDev())
.isEqualTo(1.5811, offset(0.0001));
}
@Test
- public void calculatesAMinOfZeroForAnEmptySnapshot() throws Exception {
- final Snapshot emptySnapshot = new UniformSnapshot(new long[]{ });
+ public void calculatesAMinOfZeroForAnEmptySnapshot() {
+ final Snapshot emptySnapshot = new UniformSnapshot(new long[]{});
assertThat(emptySnapshot.getMin())
.isZero();
}
@Test
- public void calculatesAMaxOfZeroForAnEmptySnapshot() throws Exception {
- final Snapshot emptySnapshot = new UniformSnapshot(new long[]{ });
+ public void calculatesAMaxOfZeroForAnEmptySnapshot() {
+ final Snapshot emptySnapshot = new UniformSnapshot(new long[]{});
assertThat(emptySnapshot.getMax())
.isZero();
}
@Test
- public void calculatesAMeanOfZeroForAnEmptySnapshot() throws Exception {
- final Snapshot emptySnapshot = new UniformSnapshot(new long[]{ });
+ public void calculatesAMeanOfZeroForAnEmptySnapshot() {
+ final Snapshot emptySnapshot = new UniformSnapshot(new long[]{});
assertThat(emptySnapshot.getMean())
.isZero();
}
@Test
- public void calculatesAStdDevOfZeroForAnEmptySnapshot() throws Exception {
- final Snapshot emptySnapshot = new UniformSnapshot(new long[]{ });
+ public void calculatesAStdDevOfZeroForAnEmptySnapshot() {
+ final Snapshot emptySnapshot = new UniformSnapshot(new long[]{});
assertThat(emptySnapshot.getStdDev())
.isZero();
}
@Test
- public void calculatesAStdDevOfZeroForASingletonSnapshot() throws Exception {
- final Snapshot singleItemSnapshot = new UniformSnapshot(new long[]{ 1 });
+ public void calculatesAStdDevOfZeroForASingletonSnapshot() {
+ final Snapshot singleItemSnapshot = new UniformSnapshot(new long[]{1});
assertThat(singleItemSnapshot.getStdDev())
.isZero();
diff --git a/metrics-core/src/test/java/com/codahale/metrics/WeightedSnapshotTest.java b/metrics-core/src/test/java/com/codahale/metrics/WeightedSnapshotTest.java
index 3e5a12d..947211c 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/WeightedSnapshotTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/WeightedSnapshotTest.java
@@ -1,129 +1,134 @@
package com.codahale.metrics;
+import com.codahale.metrics.WeightedSnapshot.WeightedSample;
import org.junit.Test;
+import org.mockito.ArgumentMatchers;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
-import com.codahale.metrics.WeightedSnapshot.WeightedSample;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.offset;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
public class WeightedSnapshotTest {
-
- static public ArrayList<WeightedSample> WeightedArray(long[] values, double[] weights) {
+
+ private static List<WeightedSample> weightedArray(long[] values, double[] weights) {
if (values.length != weights.length) {
throw new IllegalArgumentException("Mismatched lengths: " + values.length + " vs " + weights.length);
}
-
- final ArrayList<WeightedSample> samples = new ArrayList<WeightedSnapshot.WeightedSample>();
+
+ final List<WeightedSample> samples = new ArrayList<>();
for (int i = 0; i < values.length; i++) {
samples.add(new WeightedSnapshot.WeightedSample(values[i], weights[i]));
}
-
+
return samples;
}
-
+
private final Snapshot snapshot = new WeightedSnapshot(
- WeightedArray(new long[]{5, 1, 2, 3, 4}, new double[]{1, 2, 3, 2, 2}) );
+ weightedArray(new long[]{5, 1, 2, 3, 4}, new double[]{1, 2, 3, 2, 2}));
@Test
- public void smallQuantilesAreTheFirstValue() throws Exception {
+ public void smallQuantilesAreTheFirstValue() {
assertThat(snapshot.getValue(0.0))
.isEqualTo(1.0, offset(0.1));
}
@Test
- public void bigQuantilesAreTheLastValue() throws Exception {
+ public void bigQuantilesAreTheLastValue() {
assertThat(snapshot.getValue(1.0))
.isEqualTo(5.0, offset(0.1));
}
@Test(expected = IllegalArgumentException.class)
public void disallowsNotANumberQuantile() {
- snapshot.getValue( Double.NaN );
+ snapshot.getValue(Double.NaN);
}
@Test(expected = IllegalArgumentException.class)
public void disallowsNegativeQuantile() {
- snapshot.getValue( -0.5 );
+ snapshot.getValue(-0.5);
}
@Test(expected = IllegalArgumentException.class)
public void disallowsQuantileOverOne() {
- snapshot.getValue( 1.5 );
+ snapshot.getValue(1.5);
}
@Test
- public void hasAMedian() throws Exception {
+ public void hasAMedian() {
assertThat(snapshot.getMedian()).isEqualTo(3.0, offset(0.1));
}
@Test
- public void hasAp75() throws Exception {
+ public void hasAp75() {
assertThat(snapshot.get75thPercentile()).isEqualTo(4.0, offset(0.1));
}
@Test
- public void hasAp95() throws Exception {
+ public void hasAp95() {
assertThat(snapshot.get95thPercentile()).isEqualTo(5.0, offset(0.1));
}
@Test
- public void hasAp98() throws Exception {
+ public void hasAp98() {
assertThat(snapshot.get98thPercentile()).isEqualTo(5.0, offset(0.1));
}
@Test
- public void hasAp99() throws Exception {
+ public void hasAp99() {
assertThat(snapshot.get99thPercentile()).isEqualTo(5.0, offset(0.1));
}
@Test
- public void hasAp999() throws Exception {
+ public void hasAp999() {
assertThat(snapshot.get999thPercentile()).isEqualTo(5.0, offset(0.1));
}
@Test
- public void hasValues() throws Exception {
+ public void hasValues() {
assertThat(snapshot.getValues())
.containsOnly(1, 2, 3, 4, 5);
}
@Test
- public void hasASize() throws Exception {
+ public void hasASize() {
assertThat(snapshot.size())
.isEqualTo(5);
}
@Test
- public void worksWithUnderestimatedCollections() throws Exception {
- final List<WeightedSample> items = spy(WeightedArray(new long[]{5, 1, 2, 3, 4}, new double[]{1, 2, 3, 2, 2}));
- when(items.size()).thenReturn(4, 5);
+ public void worksWithUnderestimatedCollections() {
+ final List<WeightedSample> originalItems = weightedArray(new long[]{5, 1, 2, 3, 4}, new double[]{1, 2, 3, 2, 2});
+ final List<WeightedSample> spyItems = spy(originalItems);
+ doReturn(originalItems.toArray(new WeightedSample[]{})).when(spyItems).toArray(ArgumentMatchers.any(WeightedSample[].class));
+ when(spyItems.size()).thenReturn(4, 5);
- final Snapshot other = new WeightedSnapshot(items);
+ final Snapshot other = new WeightedSnapshot(spyItems);
assertThat(other.getValues())
.containsOnly(1, 2, 3, 4, 5);
}
@Test
- public void worksWithOverestimatedCollections() throws Exception {
- final List<WeightedSample> items = spy(WeightedArray(new long[]{5, 1, 2, 3, 4}, new double[]{1, 2, 3, 2, 2}));
- when(items.size()).thenReturn(6, 5);
+ public void worksWithOverestimatedCollections() {
+ final List<WeightedSample> originalItems = weightedArray(new long[]{5, 1, 2, 3, 4}, new double[]{1, 2, 3, 2, 2});
+ final List<WeightedSample> spyItems = spy(originalItems);
+ doReturn(originalItems.toArray(new WeightedSample[]{})).when(spyItems).toArray(ArgumentMatchers.any(WeightedSample[].class));
+ when(spyItems.size()).thenReturn(6, 5);
- final Snapshot other = new WeightedSnapshot(items);
+ final Snapshot other = new WeightedSnapshot(spyItems);
assertThat(other.getValues())
.containsOnly(1, 2, 3, 4, 5);
}
@Test
- public void dumpsToAStream() throws Exception {
+ public void dumpsToAStream() {
final ByteArrayOutputStream output = new ByteArrayOutputStream();
snapshot.dump(output);
@@ -133,81 +138,81 @@ public class WeightedSnapshotTest {
}
@Test
- public void calculatesTheMinimumValue() throws Exception {
+ public void calculatesTheMinimumValue() {
assertThat(snapshot.getMin())
.isEqualTo(1);
}
@Test
- public void calculatesTheMaximumValue() throws Exception {
+ public void calculatesTheMaximumValue() {
assertThat(snapshot.getMax())
.isEqualTo(5);
}
@Test
- public void calculatesTheMeanValue() throws Exception {
+ public void calculatesTheMeanValue() {
assertThat(snapshot.getMean())
.isEqualTo(2.7);
}
@Test
- public void calculatesTheStdDev() throws Exception {
+ public void calculatesTheStdDev() {
assertThat(snapshot.getStdDev())
.isEqualTo(1.2688, offset(0.0001));
}
@Test
- public void calculatesAMinOfZeroForAnEmptySnapshot() throws Exception {
+ public void calculatesAMinOfZeroForAnEmptySnapshot() {
final Snapshot emptySnapshot = new WeightedSnapshot(
- WeightedArray(new long[]{}, new double[]{}) );
+ weightedArray(new long[]{}, new double[]{}));
assertThat(emptySnapshot.getMin())
.isZero();
}
@Test
- public void calculatesAMaxOfZeroForAnEmptySnapshot() throws Exception {
+ public void calculatesAMaxOfZeroForAnEmptySnapshot() {
final Snapshot emptySnapshot = new WeightedSnapshot(
- WeightedArray(new long[]{}, new double[]{}) );
+ weightedArray(new long[]{}, new double[]{}));
assertThat(emptySnapshot.getMax())
.isZero();
}
@Test
- public void calculatesAMeanOfZeroForAnEmptySnapshot() throws Exception {
+ public void calculatesAMeanOfZeroForAnEmptySnapshot() {
final Snapshot emptySnapshot = new WeightedSnapshot(
- WeightedArray(new long[]{}, new double[]{}) );
+ weightedArray(new long[]{}, new double[]{}));
assertThat(emptySnapshot.getMean())
.isZero();
}
@Test
- public void calculatesAStdDevOfZeroForAnEmptySnapshot() throws Exception {
+ public void calculatesAStdDevOfZeroForAnEmptySnapshot() {
final Snapshot emptySnapshot = new WeightedSnapshot(
- WeightedArray(new long[]{}, new double[]{}) );
+ weightedArray(new long[]{}, new double[]{}));
assertThat(emptySnapshot.getStdDev())
.isZero();
}
@Test
- public void calculatesAStdDevOfZeroForASingletonSnapshot() throws Exception {
+ public void calculatesAStdDevOfZeroForASingletonSnapshot() {
final Snapshot singleItemSnapshot = new WeightedSnapshot(
- WeightedArray(new long[]{ 1 }, new double[]{ 1.0 }) );
+ weightedArray(new long[]{1}, new double[]{1.0}));
assertThat(singleItemSnapshot.getStdDev())
.isZero();
}
@Test
- public void expectNoOverflowForLowWeights() throws Exception {
+ public void expectNoOverflowForLowWeights() {
final Snapshot scatteredSnapshot = new WeightedSnapshot(
- WeightedArray(
- new long[]{ 1, 2, 3 },
- new double[]{ Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE }
- )
+ weightedArray(
+ new long[]{1, 2, 3},
+ new double[]{Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE}
+ )
);
assertThat(scatteredSnapshot.getMean())
@@ -217,7 +222,7 @@ public class WeightedSnapshotTest {
@Test
public void doesNotProduceNaNValues() {
WeightedSnapshot weightedSnapshot = new WeightedSnapshot(
- WeightedArray(new long[]{1, 2, 3}, new double[]{0, 0, 0}));
+ weightedArray(new long[]{1, 2, 3}, new double[]{0, 0, 0}));
assertThat(weightedSnapshot.getMean()).isEqualTo(0);
}
diff --git a/metrics-ehcache/pom.xml b/metrics-ehcache/pom.xml
index 771eab8..329e5cc 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-ehcache</artifactId>
@@ -15,16 +15,32 @@
An Ehcache wrapper providing Metrics instrumentation of caches.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.ehcache</javaModuleName>
+ <ehcache2.version>2.10.9.2</ehcache2.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
- <version>2.8.3</version>
+ <version>${ehcache2.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -32,5 +48,23 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/metrics-ehcache/src/main/java/com/codahale/metrics/ehcache/InstrumentedEhcache.java b/metrics-ehcache/src/main/java/com/codahale/metrics/ehcache/InstrumentedEhcache.java
index 69fa1eb..4ffcc92 100644
--- a/metrics-ehcache/src/main/java/com/codahale/metrics/ehcache/InstrumentedEhcache.java
+++ b/metrics-ehcache/src/main/java/com/codahale/metrics/ehcache/InstrumentedEhcache.java
@@ -1,6 +1,5 @@
package com.codahale.metrics.ehcache;
-import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import net.sf.ehcache.CacheException;
@@ -20,7 +19,7 @@ public class InstrumentedEhcache extends EhcacheDecoratorAdapter {
/**
* Instruments the given {@link Ehcache} instance with get and put timers
* and a set of gauges for Ehcache's built-in statistics:
- * <p/>
+ * <p>
* <table>
* <caption>Ehcache timered metrics</caption>
* <tr>
@@ -106,153 +105,68 @@ public class InstrumentedEhcache extends EhcacheDecoratorAdapter {
* "None", "Best Effort" or "Guaranteed".</td>
* </tr>
* </table>
- *
+ * <p>
* <b>N.B.: This enables Ehcache's sampling statistics with an accuracy
* level of "none."</b>
*
- * @param cache an {@link Ehcache} instance
- * @param registry a {@link MetricRegistry}
+ * @param cache an {@link Ehcache} instance
+ * @param registry a {@link MetricRegistry}
* @return an instrumented decorator for {@code cache}
* @see StatisticsGateway
*/
public static Ehcache instrument(MetricRegistry registry, final Ehcache cache) {
final String prefix = name(cache.getClass(), cache.getName());
- registry.register(name(prefix, "hits"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().cacheHitCount();
- }
- });
+ registry.registerGauge(name(prefix, "hits"),
+ () -> cache.getStatistics().cacheHitCount());
- registry.register(name(prefix, "in-memory-hits"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().localHeapHitCount();
- }
- });
+ registry.registerGauge(name(prefix, "in-memory-hits"),
+ () -> cache.getStatistics().localHeapHitCount());
- registry.register(name(prefix, "off-heap-hits"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().localOffHeapHitCount();
- }
- });
+ registry.registerGauge(name(prefix, "off-heap-hits"),
+ () -> cache.getStatistics().localOffHeapHitCount());
- registry.register(name(prefix, "on-disk-hits"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().localDiskHitCount();
- }
- });
+ registry.registerGauge(name(prefix, "on-disk-hits"),
+ () -> cache.getStatistics().localDiskHitCount());
- registry.register(name(prefix, "misses"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().cacheMissCount();
- }
- });
+ registry.registerGauge(name(prefix, "misses"),
+ () -> cache.getStatistics().cacheMissCount());
- registry.register(name(prefix, "in-memory-misses"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().localHeapMissCount();
- }
- });
+ registry.registerGauge(name(prefix, "in-memory-misses"),
+ () -> cache.getStatistics().localHeapMissCount());
- registry.register(name(prefix, "off-heap-misses"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().localOffHeapMissCount();
- }
- });
+ registry.registerGauge(name(prefix, "off-heap-misses"),
+ () -> cache.getStatistics().localOffHeapMissCount());
- registry.register(name(prefix, "on-disk-misses"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().localDiskMissCount();
- }
- });
+ registry.registerGauge(name(prefix, "on-disk-misses"),
+ () -> cache.getStatistics().localDiskMissCount());
- registry.register(name(prefix, "objects"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().getSize();
- }
- });
+ registry.registerGauge(name(prefix, "objects"),
+ () -> cache.getStatistics().getSize());
- registry.register(name(prefix, "in-memory-objects"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().getLocalHeapSize();
- }
- });
+ registry.registerGauge(name(prefix, "in-memory-objects"),
+ () -> cache.getStatistics().getLocalHeapSize());
- registry.register(name(prefix, "off-heap-objects"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().getLocalOffHeapSize();
- }
- });
+ registry.registerGauge(name(prefix, "off-heap-objects"),
+ () -> cache.getStatistics().getLocalOffHeapSize());
- registry.register(name(prefix, "on-disk-objects"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().getLocalDiskSize();
- }
- });
+ registry.registerGauge(name(prefix, "on-disk-objects"),
+ () -> cache.getStatistics().getLocalDiskSize());
- registry.register(name(prefix, "mean-get-time"),
- new Gauge<Double>() {
- @Override
- public Double getValue() {
- return cache.getStatistics().cacheGetOperation().latency().average().value();
- }
- });
+ registry.registerGauge(name(prefix, "mean-get-time"),
+ () -> cache.getStatistics().cacheGetOperation().latency().average().value());
- registry.register(name(prefix, "mean-search-time"),
- new Gauge<Double>() {
- @Override
- public Double getValue() {
- return cache.getStatistics().cacheSearchOperation().latency().average().value();
- }
- });
+ registry.registerGauge(name(prefix, "mean-search-time"),
+ () -> cache.getStatistics().cacheSearchOperation().latency().average().value());
- registry.register(name(prefix, "eviction-count"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().cacheEvictionOperation().count().value();
- }
- });
+ registry.registerGauge(name(prefix, "eviction-count"),
+ () -> cache.getStatistics().cacheEvictionOperation().count().value());
- registry.register(name(prefix, "searches-per-second"),
- new Gauge<Double>() {
- @Override
- public Double getValue() {
- return cache.getStatistics().cacheSearchOperation().rate().value();
- }
- });
+ registry.registerGauge(name(prefix, "searches-per-second"),
+ () -> cache.getStatistics().cacheSearchOperation().rate().value());
- registry.register(name(prefix, "writer-queue-size"),
- new Gauge<Long>() {
- @Override
- public Long getValue() {
- return cache.getStatistics().getWriterQueueLength();
- }
- });
+ registry.registerGauge(name(prefix, "writer-queue-size"),
+ () -> cache.getStatistics().getWriterQueueLength());
return new InstrumentedEhcache(registry, cache);
}
diff --git a/metrics-ehcache/src/test/java/com/codahale/metrics/ehcache/InstrumentedCacheDecoratorFactoryTest.java b/metrics-ehcache/src/test/java/com/codahale/metrics/ehcache/InstrumentedCacheDecoratorFactoryTest.java
index f004320..c9177e0 100644
--- a/metrics-ehcache/src/test/java/com/codahale/metrics/ehcache/InstrumentedCacheDecoratorFactoryTest.java
+++ b/metrics-ehcache/src/test/java/com/codahale/metrics/ehcache/InstrumentedCacheDecoratorFactoryTest.java
@@ -6,14 +6,12 @@ import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
-import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import static com.codahale.metrics.MetricRegistry.name;
+import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assume.assumeThat;
public class InstrumentedCacheDecoratorFactoryTest {
private static final CacheManager MANAGER = CacheManager.create();
@@ -22,15 +20,13 @@ public class InstrumentedCacheDecoratorFactoryTest {
private Ehcache cache;
@Before
- public void setUp() throws Exception {
- this.cache = MANAGER.getEhcache("test-config");
- assumeThat(cache, is(CoreMatchers.notNullValue()));
-
+ public void setUp() {
+ this.cache = requireNonNull(MANAGER.getEhcache("test-config"));
this.registry = SharedMetricRegistries.getOrCreate("cache-metrics");
}
@Test
- public void measuresGets() throws Exception {
+ public void measuresGets() {
cache.get("woo");
assertThat(registry.timer(name(Cache.class, "test-config", "gets")).getCount())
@@ -39,7 +35,7 @@ public class InstrumentedCacheDecoratorFactoryTest {
}
@Test
- public void measuresPuts() throws Exception {
+ public void measuresPuts() {
cache.put(new Element("woo", "whee"));
assertThat(registry.timer(name(Cache.class, "test-config", "puts")).getCount())
diff --git a/metrics-ehcache/src/test/java/com/codahale/metrics/ehcache/InstrumentedEhcacheTest.java b/metrics-ehcache/src/test/java/com/codahale/metrics/ehcache/InstrumentedEhcacheTest.java
index d97dec3..a2f8636 100644
--- a/metrics-ehcache/src/test/java/com/codahale/metrics/ehcache/InstrumentedEhcacheTest.java
+++ b/metrics-ehcache/src/test/java/com/codahale/metrics/ehcache/InstrumentedEhcacheTest.java
@@ -12,6 +12,7 @@ import org.junit.Test;
import static com.codahale.metrics.MetricRegistry.name;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
public class InstrumentedEhcacheTest {
private static final CacheManager MANAGER = CacheManager.create();
@@ -20,14 +21,35 @@ public class InstrumentedEhcacheTest {
private Ehcache cache;
@Before
- public void setUp() throws Exception {
+ public void setUp() {
final Cache c = new Cache(new CacheConfiguration("test", 100));
MANAGER.addCache(c);
this.cache = InstrumentedEhcache.instrument(registry, c);
+ assertThat(registry.getGauges().entrySet().stream()
+ .map(e -> entry(e.getKey(), e.getValue().getValue())))
+ .containsOnly(
+ entry("net.sf.ehcache.Cache.test.eviction-count", 0L),
+ entry("net.sf.ehcache.Cache.test.hits", 0L),
+ entry("net.sf.ehcache.Cache.test.in-memory-hits", 0L),
+ entry("net.sf.ehcache.Cache.test.in-memory-misses", 0L),
+ entry("net.sf.ehcache.Cache.test.in-memory-objects", 0L),
+ entry("net.sf.ehcache.Cache.test.mean-get-time", Double.NaN),
+ entry("net.sf.ehcache.Cache.test.mean-search-time", Double.NaN),
+ entry("net.sf.ehcache.Cache.test.misses", 0L),
+ entry("net.sf.ehcache.Cache.test.objects", 0L),
+ entry("net.sf.ehcache.Cache.test.off-heap-hits", 0L),
+ entry("net.sf.ehcache.Cache.test.off-heap-misses", 0L),
+ entry("net.sf.ehcache.Cache.test.off-heap-objects", 0L),
+ entry("net.sf.ehcache.Cache.test.on-disk-hits", 0L),
+ entry("net.sf.ehcache.Cache.test.on-disk-misses", 0L),
+ entry("net.sf.ehcache.Cache.test.on-disk-objects", 0L),
+ entry("net.sf.ehcache.Cache.test.searches-per-second", 0.0),
+ entry("net.sf.ehcache.Cache.test.writer-queue-size", 0L)
+ );
}
@Test
- public void measuresGetsAndPuts() throws Exception {
+ public void measuresGetsAndPuts() {
cache.get("woo");
cache.put(new Element("woo", "whee"));
diff --git a/metrics-ehcache/src/test/resources/ehcache.xml b/metrics-ehcache/src/test/resources/ehcache.xml
index 9c6c145..6e790fd 100644
--- a/metrics-ehcache/src/test/resources/ehcache.xml
+++ b/metrics-ehcache/src/test/resources/ehcache.xml
@@ -4,16 +4,16 @@
monitoring="autodetect" dynamicConfig="true">
<cache name="test-config"
- maxElementsInMemory="30"
- eternal="false"
- overflowToDisk="false"
- timeToIdleSeconds="1000"
- timeToLiveSeconds="3600"
- memoryStoreEvictionPolicy="LFU"
- transactionalMode="off">
+ maxElementsInMemory="30"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="1000"
+ timeToLiveSeconds="3600"
+ memoryStoreEvictionPolicy="LFU"
+ transactionalMode="off">
- <cacheDecoratorFactory class="com.codahale.metrics.ehcache.InstrumentedCacheDecoratorFactory"
- properties="metric-registry-name=cache-metrics" />
+ <cacheDecoratorFactory class="com.codahale.metrics.ehcache.InstrumentedCacheDecoratorFactory"
+ properties="metric-registry-name=cache-metrics"/>
</cache>
diff --git a/metrics-ganglia/pom.xml b/metrics-ganglia/pom.xml
deleted file mode 100644
index 0811d1e..0000000
--- a/metrics-ganglia/pom.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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.6</version>
- </parent>
-
- <artifactId>metrics-ganglia</artifactId>
- <name>Ganglia Integration for Metrics</name>
- <packaging>bundle</packaging>
- <description>
- A reporter for Metrics which announces measurements to a Ganglia cluster.
- </description>
-
- <dependencies>
- <dependency>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>info.ganglia.gmetric4j</groupId>
- <artifactId>gmetric4j</artifactId>
- <version>1.0.7</version>
- </dependency>
- </dependencies>
-</project>
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
deleted file mode 100644
index 2e0fd29..0000000
--- a/metrics-ganglia/src/main/java/com/codahale/metrics/ganglia/GangliaReporter.java
+++ /dev/null
@@ -1,422 +0,0 @@
-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;
-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.
- *
- * @see <a href="http://ganglia.sourceforge.net/">Ganglia Monitoring System</a>
- */
-public class GangliaReporter extends ScheduledReporter {
-
- private static final Pattern SLASHES = Pattern.compile("\\\\");
-
- /**
- * Returns a new {@link Builder} for {@link GangliaReporter}.
- *
- * @param registry the registry to report
- * @return a {@link Builder} instance for a {@link GangliaReporter}
- */
- public static Builder forRegistry(MetricRegistry registry) {
- return new Builder(registry);
- }
-
- /**
- * A builder for {@link GangliaReporter} instances. Defaults to using a {@code tmax} of {@code 60},
- * a {@code dmax} of {@code 0}, converting rates to events/second, converting durations to
- * milliseconds, and not filtering metrics.
- */
- public static class Builder {
- private final MetricRegistry registry;
- private String prefix;
- private int tMax;
- private int dMax;
- 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;
- this.tMax = 60;
- this.dMax = 0;
- 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;
- }
-
- /**
- * Use the given {@code tmax} value when announcing metrics.
- *
- * @param tMax the desired gmond {@code tmax} value
- * @return {@code this}
- */
- public Builder withTMax(int tMax) {
- this.tMax = tMax;
- return this;
- }
-
- /**
- * Prefix all metric names with the given string.
- *
- * @param prefix the prefix for all metric names
- * @return {@code this}
- */
- public Builder prefixedWith(String prefix) {
- this.prefix = prefix;
- return this;
- }
-
- /**
- * Use the given {@code dmax} value when announcing metrics.
- *
- * @param dMax the desired gmond {@code dmax} value
- * @return {@code this}
- */
- public Builder withDMax(int dMax) {
- this.dMax = dMax;
- return this;
- }
-
- /**
- * Convert rates to the given time unit.
- *
- * @param rateUnit a unit of time
- * @return {@code this}
- */
- public Builder convertRatesTo(TimeUnit rateUnit) {
- this.rateUnit = rateUnit;
- return this;
- }
-
- /**
- * Convert durations to the given time unit.
- *
- * @param durationUnit a unit of time
- * @return {@code this}
- */
- public Builder convertDurationsTo(TimeUnit durationUnit) {
- this.durationUnit = durationUnit;
- return this;
- }
-
- /**
- * Only report metrics which match the given filter.
- *
- * @param filter a {@link MetricFilter}
- * @return {@code this}
- */
- public Builder filter(MetricFilter filter) {
- this.filter = filter;
- return this;
- }
-
- /**
- * 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.
- *
- * @param gmetric the client to use for announcing metrics
- * @return a {@link GangliaReporter}
- */
- public GangliaReporter build(GMetric gmetric) {
- return new GangliaReporter(registry, gmetric, null, prefix, tMax, dMax, rateUnit, durationUnit, filter,
- executor, shutdownExecutorOnStop, disabledMetricAttributes);
- }
-
- /**
- * Builds a {@link GangliaReporter} with the given properties, announcing metrics to the
- * given {@link GMetric} client.
- *
- * @param gmetrics the clients to use for announcing metrics
- * @return a {@link GangliaReporter}
- */
- public GangliaReporter build(GMetric... gmetrics) {
- return new GangliaReporter(registry, null, gmetrics, prefix, tMax, dMax, rateUnit, durationUnit,
- filter, executor, shutdownExecutorOnStop , disabledMetricAttributes);
- }
- }
-
- private static final Logger LOGGER = LoggerFactory.getLogger(GangliaReporter.class);
-
- private final GMetric gmetric;
- private final GMetric[] gmetrics;
- private final String prefix;
- private final int tMax;
- private final int dMax;
-
- private GangliaReporter(MetricRegistry registry,
- GMetric gmetric,
- GMetric[] gmetrics,
- String prefix,
- int tMax,
- int dMax,
- TimeUnit rateUnit,
- TimeUnit 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;
- this.tMax = tMax;
- this.dMax = dMax;
- }
-
- @Override
- public void report(SortedMap<String, Gauge> gauges,
- SortedMap<String, Counter> counters,
- SortedMap<String, Histogram> histograms,
- SortedMap<String, Meter> meters,
- SortedMap<String, Timer> timers) {
- for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
- reportGauge(entry.getKey(), entry.getValue());
- }
-
- for (Map.Entry<String, Counter> entry : counters.entrySet()) {
- reportCounter(entry.getKey(), entry.getValue());
- }
-
- for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
- reportHistogram(entry.getKey(), entry.getValue());
- }
-
- for (Map.Entry<String, Meter> entry : meters.entrySet()) {
- reportMeter(entry.getKey(), entry.getValue());
- }
-
- for (Map.Entry<String, Timer> entry : timers.entrySet()) {
- reportTimer(entry.getKey(), entry.getValue());
- }
- }
-
- private void reportTimer(String name, Timer timer) {
- final String sanitizedName = escapeSlashes(name);
- final String group = group(name);
- try {
- final Snapshot snapshot = timer.getSnapshot();
-
- 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());
-
- announceIfEnabled(P50, sanitizedName, group, convertDuration(snapshot.getMedian()), getDurationUnit());
- announceIfEnabled(P75, sanitizedName,
- group,
- convertDuration(snapshot.get75thPercentile()),
- getDurationUnit());
- announceIfEnabled(P95, sanitizedName,
- group,
- convertDuration(snapshot.get95thPercentile()),
- getDurationUnit());
- announceIfEnabled(P98, sanitizedName,
- group,
- convertDuration(snapshot.get98thPercentile()),
- getDurationUnit());
- announceIfEnabled(P99, sanitizedName,
- group,
- convertDuration(snapshot.get99thPercentile()),
- getDurationUnit());
- announceIfEnabled(P999, sanitizedName,
- group,
- convertDuration(snapshot.get999thPercentile()),
- getDurationUnit());
-
- reportMetered(sanitizedName, timer, group, "calls");
- } catch (GangliaException e) {
- LOGGER.warn("Unable to report timer {}", sanitizedName, e);
- }
- }
-
- private void reportMeter(String name, Meter meter) {
- final String sanitizedName = escapeSlashes(name);
- final String group = group(name);
- try {
- reportMetered(sanitizedName, meter, group, "events");
- } catch (GangliaException e) {
- LOGGER.warn("Unable to report meter {}", name, e);
- }
- }
-
- private void reportMetered(String name, Metered meter, String group, String eventName) throws GangliaException {
- final String unit = eventName + '/' + getRateUnit();
- 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) {
- final String sanitizedName = escapeSlashes(name);
- final String group = group(name);
- try {
- final Snapshot snapshot = histogram.getSnapshot();
-
- 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);
- }
- }
-
- private void reportCounter(String name, Counter counter) {
- final String sanitizedName = escapeSlashes(name);
- final String group = group(name);
- try {
- announce(prefix(sanitizedName, COUNT.getCode()), group, Long.toString(counter.getCount()), GMetricType.DOUBLE, "");
- } catch (GangliaException e) {
- LOGGER.warn("Unable to report counter {}", name, e);
- }
- }
-
- private void reportGauge(String name, Gauge gauge) {
- final String sanitizedName = escapeSlashes(name);
- final String group = group(name);
- final Object obj = gauge.getValue();
- final String value = String.valueOf(obj);
- final GMetricType type = detectType(obj);
- try {
- announce(name(prefix, sanitizedName), group, value, type, "");
- } catch (GangliaException e) {
- LOGGER.warn("Unable to report gauge {}", name, e);
- }
- }
-
- private static final double MIN_VAL = 1E-300;
-
- 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(prefix(metricName, metricAttribute.getCode()), group, string, 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 {
- if (gmetric != null) {
- gmetric.announce(name, value, type, units, GMetricSlope.BOTH, tMax, dMax, group);
- } else {
- for (GMetric gmetric : gmetrics) {
- gmetric.announce(name, value, type, units, GMetricSlope.BOTH, tMax, dMax, group);
- }
- }
- }
-
- private GMetricType detectType(Object o) {
- if (o instanceof Float) {
- return GMetricType.FLOAT;
- } else if (o instanceof Double) {
- return GMetricType.DOUBLE;
- } else if (o instanceof Byte) {
- return GMetricType.INT8;
- } else if (o instanceof Short) {
- return GMetricType.INT16;
- } else if (o instanceof Integer) {
- return GMetricType.INT32;
- } else if (o instanceof Long) {
- return GMetricType.DOUBLE;
- }
- return GMetricType.STRING;
- }
-
- private String group(String name) {
- final int i = name.lastIndexOf('.');
- if (i < 0) {
- return "";
- }
- return name.substring(0, i);
- }
-
- private String prefix(String name, String n) {
- return name(prefix, name, n);
- }
-
- // ganglia metric names can't contain slashes.
- private String escapeSlashes(String name) {
- return SLASHES.matcher(name).replaceAll("_");
- }
-}
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
deleted file mode 100644
index 470bd58..0000000
--- a/metrics-ganglia/src/test/java/com/codahale/metrics/ganglia/GangliaReporterTest.java
+++ /dev/null
@@ -1,303 +0,0 @@
-package com.codahale.metrics.ganglia;
-
-import com.codahale.metrics.*;
-import info.ganglia.gmetric4j.gmetric.GMetric;
-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;
-
-import static org.mockito.Mockito.*;
-
-public class GangliaReporterTest {
- private final GMetric ganglia = mock(GMetric.class);
- private final MetricRegistry registry = mock(MetricRegistry.class);
- private final GangliaReporter reporter = GangliaReporter.forRegistry(registry)
- .prefixedWith("m")
- .withTMax(60)
- .withDMax(0)
- .convertRatesTo(TimeUnit.SECONDS)
- .convertDurationsTo(TimeUnit.MILLISECONDS)
- .filter(MetricFilter.ALL)
- .build(ganglia);
-
- @Test
- public void reportsStringGaugeValues() throws Exception {
- reporter.report(map("gauge", gauge("value")),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.gauge", "value", GMetricType.STRING, "", GMetricSlope.BOTH, 60, 0, "");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void escapeSlashesInMetricNames() throws Exception {
- reporter.report(map("gauge_with\\slashes", gauge("value")),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.gauge_with_slashes", "value", GMetricType.STRING, "", GMetricSlope.BOTH, 60, 0, "");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsByteGaugeValues() throws Exception {
- reporter.report(map("gauge", gauge((byte) 1)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.gauge", "1", GMetricType.INT8, "", GMetricSlope.BOTH, 60, 0, "");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsShortGaugeValues() throws Exception {
- reporter.report(map("gauge", gauge((short) 1)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.gauge", "1", GMetricType.INT16, "", GMetricSlope.BOTH, 60, 0, "");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsIntegerGaugeValues() throws Exception {
- reporter.report(map("gauge", gauge(1)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.gauge", "1", GMetricType.INT32, "", GMetricSlope.BOTH, 60, 0, "");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsLongGaugeValues() throws Exception {
- reporter.report(map("gauge", gauge(1L)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.gauge", "1", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsFloatGaugeValues() throws Exception {
- reporter.report(map("gauge", gauge(1.0f)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.gauge", "1.0", GMetricType.FLOAT, "", GMetricSlope.BOTH, 60, 0, "");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsDoubleGaugeValues() throws Exception {
- reporter.report(map("gauge", gauge(1.0)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.gauge", "1.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsCounterValues() throws Exception {
- final Counter counter = mock(Counter.class);
- when(counter.getCount()).thenReturn(100L);
-
- reporter.report(this.<Gauge>map(),
- map("test.counter", counter),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.test.counter.count", "100", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsHistogramValues() throws Exception {
- 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);
-
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- map("test.histogram", histogram),
- this.<Meter>map(),
- this.<Timer>map());
-
- verify(ganglia).announce("m.test.histogram.count", "1", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.max", "2", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.mean", "3.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.min", "4", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.stddev", "5.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.p50", "6.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.p75", "7.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.p95", "8.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.p98", "9.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.p99", "10.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.histogram.p999", "11.0", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0, "test");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsMeterValues() throws Exception {
- 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);
-
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- map("test.meter", meter),
- this.<Timer>map());
-
- verify(ganglia).announce("m.test.meter.count", "1", GMetricType.DOUBLE, "events", GMetricSlope.BOTH, 60, 0, "test");
- verify(ganglia).announce("m.test.meter.mean_rate", "2.0", GMetricType.DOUBLE, "events/second", 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");
- verify(ganglia).announce("m.test.meter.m15_rate", "5.0", GMetricType.DOUBLE, "events/second", GMetricSlope.BOTH, 60, 0, "test");
- verifyNoMoreInteractions(ganglia);
- }
-
- @Test
- public void reportsTimerValues() throws Exception {
- 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);
-
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- map("test.another.timer", timer));
-
- verify(ganglia).announce("m.test.another.timer.max", "100.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.mean", "200.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.min", "300.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.stddev", "400.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.p50", "500.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.p75", "600.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.p95", "700.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.p98", "800.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.p99", "900.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.p999", "1000.0", GMetricType.DOUBLE, "milliseconds", GMetricSlope.BOTH, 60, 0, "test.another");
-
- verify(ganglia).announce("m.test.another.timer.count", "1", GMetricType.DOUBLE, "calls", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.mean_rate", "2.0", GMetricType.DOUBLE, "calls/second", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.m1_rate", "3.0", GMetricType.DOUBLE, "calls/second", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.m5_rate", "4.0", GMetricType.DOUBLE, "calls/second", GMetricSlope.BOTH, 60, 0, "test.another");
- verify(ganglia).announce("m.test.another.timer.m15_rate", "5.0", GMetricType.DOUBLE, "calls/second", GMetricSlope.BOTH, 60, 0, "test.another");
-
- 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>();
- }
-
- private <T> SortedMap<String, T> map(String name, T metric) {
- final TreeMap<String, T> map = new TreeMap<String, T>();
- map.put(name, metric);
- return map;
- }
-
- private <T> Gauge gauge(T value) {
- final Gauge gauge = mock(Gauge.class);
- when(gauge.getValue()).thenReturn(value);
- return gauge;
- }
-}
diff --git a/metrics-graphite/pom.xml b/metrics-graphite/pom.xml
index 605d5e6..a0ed9e1 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-graphite</artifactId>
@@ -15,22 +15,80 @@
A reporter for Metrics which announces measurements to a Graphite server.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.graphite</javaModuleName>
+ <rabbitmq.version>5.20.0</rabbitmq.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>${rabbitmq.version}</version>
- <optional>true</optional>
+ <exclusions>
+ <exclusion>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
- <version>2.5.3</version>
+ <version>2.7.3</version>
<scope>test</scope>
</dependency>
</dependencies>
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 5761a7b..0baf32e 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
@@ -4,19 +4,23 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.SocketFactory;
-
-import java.io.*;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
+
/**
* A client to a Carbon server via TCP.
*/
public class Graphite implements GraphiteSender {
// this may be optimistic about Carbon/Graphite
- private static final Charset UTF_8 = Charset.forName("UTF-8");
private final String hostname;
private final int port;
@@ -35,7 +39,7 @@ public class Graphite implements GraphiteSender {
* {@link SocketFactory}.
*
* @param hostname The hostname of the Carbon server
- * @param port The port of the Carbon server
+ * @param port The port of the Carbon server
*/
public Graphite(String hostname, int port) {
this(hostname, port, SocketFactory.getDefault());
@@ -44,8 +48,8 @@ public class Graphite implements GraphiteSender {
/**
* Creates a new client which connects to the given address and socket factory.
*
- * @param hostname The hostname of the Carbon server
- * @param port The port of the Carbon server
+ * @param hostname The hostname of the Carbon server
+ * @param port The port of the Carbon server
* @param socketFactory the socket factory
*/
public Graphite(String hostname, int port, SocketFactory socketFactory) {
@@ -56,17 +60,25 @@ public class Graphite implements GraphiteSender {
* Creates a new client which connects to the given address and socket factory using the given
* character set.
*
- * @param hostname The hostname of the Carbon server
- * @param port The port of the Carbon server
+ * @param hostname The hostname of the Carbon server
+ * @param port The port of the Carbon server
* @param socketFactory the socket factory
* @param charset the character set used by the server
*/
public Graphite(String hostname, int port, SocketFactory socketFactory, Charset charset) {
+ if (hostname == null || hostname.isEmpty()) {
+ throw new IllegalArgumentException("hostname must not be null or empty");
+ }
+
+ if (port < 0 || port > 65535) {
+ throw new IllegalArgumentException("port must be a valid IP port (0-65535)");
+ }
+
this.hostname = hostname;
this.port = port;
this.address = null;
- this.socketFactory = socketFactory;
- this.charset = charset;
+ this.socketFactory = requireNonNull(socketFactory, "socketFactory must not be null");
+ this.charset = requireNonNull(charset, "charset must not be null");
}
/**
@@ -100,9 +112,9 @@ public class Graphite implements GraphiteSender {
public Graphite(InetSocketAddress address, SocketFactory socketFactory, Charset charset) {
this.hostname = null;
this.port = -1;
- this.address = address;
- this.socketFactory = socketFactory;
- this.charset = charset;
+ this.address = requireNonNull(address, "address must not be null");
+ this.socketFactory = requireNonNull(socketFactory, "socketFactory must not be null");
+ this.charset = requireNonNull(charset, "charset must not be null");
}
@Override
@@ -111,16 +123,16 @@ public class Graphite implements GraphiteSender {
throw new IllegalStateException("Already connected");
}
InetSocketAddress address = this.address;
- if (address == null) {
+ // the previous dns retry logic did not work, as address.getAddress would always return the cached value
+ // this version of the simplified logic will always cause a dns request if hostname has been supplied.
+ // InetAddress.getByName forces the dns lookup
+ // if an InetSocketAddress was supplied at create time that will take precedence.
+ if (address == null || address.getHostName() == null && hostname != null) {
address = new InetSocketAddress(hostname, port);
}
- if (address.getAddress() == null) {
- // retry lookup, just in case the DNS changed
- address = new InetSocketAddress(address.getHostName(),address.getPort());
- if (address.getAddress() == null) {
- throw new UnknownHostException(address.getHostName());
- }
+ if (address.getAddress() == null) {
+ throw new UnknownHostException(address.getHostName());
}
this.socket = socketFactory.createSocket(address.getAddress(), address.getPort());
@@ -129,7 +141,7 @@ public class Graphite implements GraphiteSender {
@Override
public boolean isConnected() {
- return socket != null && socket.isConnected() && !socket.isClosed();
+ return socket != null && socket.isConnected() && !socket.isClosed();
}
@Override
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 93e6c75..9784ef9 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
@@ -7,16 +7,15 @@ import com.rabbitmq.client.DefaultSocketConfigurator;
import java.io.IOException;
import java.net.Socket;
-import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
/**
* A rabbit-mq client to a Carbon server.
*/
public class GraphiteRabbitMQ implements GraphiteSender {
- private static final Charset UTF_8 = Charset.forName("UTF-8");
-
private static final Integer DEFAULT_RABBIT_CONNECTION_TIMEOUT_MS = 500;
private static final Integer DEFAULT_RABBIT_SOCKET_TIMEOUT_MS = 5000;
private static final Integer DEFAULT_RABBIT_REQUESTED_HEARTBEAT_SEC = 10;
@@ -91,7 +90,7 @@ public class GraphiteRabbitMQ implements GraphiteSender {
this.connectionFactory = new ConnectionFactory();
- connectionFactory.setSocketConfigurator(new DefaultSocketConfigurator() {
+ connectionFactory.setSocketConfigurator(new DefaultSocketConfigurator() {
@Override
public void configure(Socket socket) throws IOException {
super.configure(socket);
@@ -132,12 +131,7 @@ public class GraphiteRabbitMQ implements GraphiteSender {
final String sanitizedName = sanitize(name);
final String sanitizedValue = sanitize(value);
- final String message =
- new StringBuilder()
- .append(sanitizedName).append(' ')
- .append(sanitizedValue).append(' ')
- .append(Long.toString(timestamp)).append('\n').toString();
-
+ final String message = sanitizedName + ' ' + sanitizedValue + ' ' + Long.toString(timestamp) + '\n';
channel.basicPublish(exchange, sanitizedName, null, message.getBytes(UTF_8));
} catch (IOException e) {
failures++;
@@ -147,7 +141,7 @@ public class GraphiteRabbitMQ implements GraphiteSender {
@Override
public void flush() throws IOException {
- // Nothing to do
+ // Nothing to do
}
@Override
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 f11803e..62a042c 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,13 +1,21 @@
package com.codahale.metrics.graphite;
-import com.codahale.metrics.*;
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metered;
+import com.codahale.metrics.MetricAttribute;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.ScheduledReporter;
+import com.codahale.metrics.Snapshot;
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;
@@ -15,8 +23,23 @@ import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
-
-import static com.codahale.metrics.MetricAttribute.*;
+import java.util.function.DoubleFunction;
+
+import static com.codahale.metrics.MetricAttribute.COUNT;
+import static com.codahale.metrics.MetricAttribute.M15_RATE;
+import static com.codahale.metrics.MetricAttribute.M1_RATE;
+import static com.codahale.metrics.MetricAttribute.M5_RATE;
+import static com.codahale.metrics.MetricAttribute.MAX;
+import static com.codahale.metrics.MetricAttribute.MEAN;
+import static com.codahale.metrics.MetricAttribute.MEAN_RATE;
+import static com.codahale.metrics.MetricAttribute.MIN;
+import static com.codahale.metrics.MetricAttribute.P50;
+import static com.codahale.metrics.MetricAttribute.P75;
+import static com.codahale.metrics.MetricAttribute.P95;
+import static com.codahale.metrics.MetricAttribute.P98;
+import static com.codahale.metrics.MetricAttribute.P99;
+import static com.codahale.metrics.MetricAttribute.P999;
+import static com.codahale.metrics.MetricAttribute.STDDEV;
/**
* A reporter which publishes metric values to a Graphite server.
@@ -49,6 +72,8 @@ public class GraphiteReporter extends ScheduledReporter {
private ScheduledExecutorService executor;
private boolean shutdownExecutorOnStop;
private Set<MetricAttribute> disabledMetricAttributes;
+ private boolean addMetricAttributesAsTags;
+ private DoubleFunction<String> floatingPointFormatter;
private Builder(MetricRegistry registry) {
this.registry = registry;
@@ -60,6 +85,8 @@ public class GraphiteReporter extends ScheduledReporter {
this.executor = null;
this.shutdownExecutorOnStop = true;
this.disabledMetricAttributes = Collections.emptySet();
+ this.addMetricAttributesAsTags = false;
+ this.floatingPointFormatter = DEFAULT_FP_FORMATTER;
}
/**
@@ -147,7 +174,7 @@ 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}
+ * @param disabledMetricAttributes a set of {@link MetricAttribute}
* @return {@code this}
*/
public Builder disabledMetricAttributes(Set<MetricAttribute> disabledMetricAttributes) {
@@ -155,10 +182,39 @@ public class GraphiteReporter extends ScheduledReporter {
return this;
}
+
+ /**
+ * Specifies whether or not metric attributes (e.g. "p999", "stddev" or "m15") should be reported in the traditional dot delimited format or in the tag based format.
+ * Without tags (default): `my.metric.p99`
+ * With tags: `my.metric;metricattribute=p99`
+ *
+ * Note that this setting only modifies the metric attribute, and will not convert any other portion of the metric name to use tags.
+ * For mor information on Graphite tag support see https://graphite.readthedocs.io/en/latest/tags.html
+ * See {@link MetricAttribute}.
+ *
+ * @param addMetricAttributesAsTags if true, then metric attributes will be added as tags
+ * @return {@code this}
+ */
+ public Builder addMetricAttributesAsTags(boolean addMetricAttributesAsTags) {
+ this.addMetricAttributesAsTags = addMetricAttributesAsTags;
+ return this;
+ }
+
+ /**
+ * Use custom floating point formatter.
+ *
+ * @param floatingPointFormatter a custom formatter for floating point values
+ * @return {@code this}
+ */
+ public Builder withFloatingPointFormatter(DoubleFunction<String> floatingPointFormatter) {
+ this.floatingPointFormatter = floatingPointFormatter;
+ return this;
+ }
+
/**
* Builds a {@link GraphiteReporter} with the given properties, sending metrics using the
* given {@link GraphiteSender}.
- *
+ * <p>
* Present for binary compatibility
*
* @param graphite a {@link Graphite}
@@ -177,57 +233,135 @@ public class GraphiteReporter extends ScheduledReporter {
*/
public GraphiteReporter build(GraphiteSender graphite) {
return new GraphiteReporter(registry,
- graphite,
- clock,
- prefix,
- rateUnit,
- durationUnit,
- filter,
- executor,
- shutdownExecutorOnStop,
- disabledMetricAttributes);
+ graphite,
+ clock,
+ prefix,
+ rateUnit,
+ durationUnit,
+ filter,
+ executor,
+ shutdownExecutorOnStop,
+ disabledMetricAttributes,
+ addMetricAttributesAsTags,
+ floatingPointFormatter);
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(GraphiteReporter.class);
+ // the Carbon plaintext format is pretty underspecified, but it seems like it just wants US-formatted digits
+ private static final DoubleFunction<String> DEFAULT_FP_FORMATTER = fp -> String.format(Locale.US, "%2.2f", fp);
private final GraphiteSender graphite;
private final Clock clock;
private final String prefix;
+ private final boolean addMetricAttributesAsTags;
+ private final DoubleFunction<String> floatingPointFormatter;
+
+
+ /**
+ * 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
+ * @param disabledMetricAttributes do not report specific metric attributes
+ */
+ protected GraphiteReporter(MetricRegistry registry,
+ GraphiteSender graphite,
+ Clock clock,
+ String prefix,
+ TimeUnit rateUnit,
+ TimeUnit durationUnit,
+ MetricFilter filter,
+ ScheduledExecutorService executor,
+ boolean shutdownExecutorOnStop,
+ Set<MetricAttribute> disabledMetricAttributes) {
+ this(registry, graphite, clock, prefix, rateUnit, durationUnit, filter, executor, shutdownExecutorOnStop,
+ disabledMetricAttributes, false);
+ }
+
+
+ /**
+ * 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
+ * @param disabledMetricAttributes do not report specific metric attributes
+ * @param addMetricAttributesAsTags if true, then add metric attributes as tags instead of suffixes
+ */
+ protected GraphiteReporter(MetricRegistry registry,
+ GraphiteSender graphite,
+ Clock clock,
+ String prefix,
+ TimeUnit rateUnit,
+ TimeUnit durationUnit,
+ MetricFilter filter,
+ ScheduledExecutorService executor,
+ boolean shutdownExecutorOnStop,
+ Set<MetricAttribute> disabledMetricAttributes,
+ boolean addMetricAttributesAsTags) {
+ this(registry, graphite, clock, prefix, rateUnit, durationUnit, filter, executor, shutdownExecutorOnStop,
+ disabledMetricAttributes, addMetricAttributesAsTags, DEFAULT_FP_FORMATTER);
+ }
/**
* 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
+ * @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
+ * @param disabledMetricAttributes do not report specific metric attributes
+ * @param addMetricAttributesAsTags if true, then add metric attributes as tags instead of suffixes
+ * @param floatingPointFormatter custom floating point formatter
*/
protected GraphiteReporter(MetricRegistry registry,
- GraphiteSender graphite,
- Clock clock,
- String prefix,
- TimeUnit rateUnit,
- TimeUnit durationUnit,
- MetricFilter filter,
- ScheduledExecutorService executor,
- boolean shutdownExecutorOnStop,
- Set<MetricAttribute> disabledMetricAttributes) {
+ GraphiteSender graphite,
+ Clock clock,
+ String prefix,
+ TimeUnit rateUnit,
+ TimeUnit durationUnit,
+ MetricFilter filter,
+ ScheduledExecutorService executor,
+ boolean shutdownExecutorOnStop,
+ Set<MetricAttribute> disabledMetricAttributes,
+ boolean addMetricAttributesAsTags,
+ DoubleFunction<String> floatingPointFormatter) {
super(registry, "graphite-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,
disabledMetricAttributes);
this.graphite = graphite;
this.clock = clock;
this.prefix = prefix;
+ this.addMetricAttributesAsTags = addMetricAttributesAsTags;
+ this.floatingPointFormatter = floatingPointFormatter;
}
@Override
+ @SuppressWarnings("rawtypes")
public void report(SortedMap<String, Gauge> gauges,
SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms,
@@ -322,24 +456,24 @@ public class GraphiteReporter extends ScheduledReporter {
}
private void sendIfEnabled(MetricAttribute type, String name, double value, long timestamp) throws IOException {
- if (getDisabledMetricAttributes().contains(type)){
+ if (getDisabledMetricAttributes().contains(type)) {
return;
}
- graphite.send(prefix(name, type.getCode()), format(value), timestamp);
+ graphite.send(prefix(appendMetricAttribute(name, type.getCode())), format(value), timestamp);
}
private void sendIfEnabled(MetricAttribute type, String name, long value, long timestamp) throws IOException {
- if (getDisabledMetricAttributes().contains(type)){
+ if (getDisabledMetricAttributes().contains(type)) {
return;
}
- graphite.send(prefix(name, type.getCode()), format(value), timestamp);
+ graphite.send(prefix(appendMetricAttribute(name, type.getCode())), format(value), timestamp);
}
private void reportCounter(String name, Counter counter, long timestamp) throws IOException {
- graphite.send(prefix(name, COUNT.getCode()), format(counter.getCount()), timestamp);
+ graphite.send(prefix(appendMetricAttribute(name, COUNT.getCode())), format(counter.getCount()), timestamp);
}
- private void reportGauge(String name, Gauge gauge, long timestamp) throws IOException {
+ private void reportGauge(String name, Gauge<?> gauge, long timestamp) throws IOException {
final String value = format(gauge.getValue());
if (value != null) {
graphite.send(prefix(name), value, timestamp);
@@ -359,18 +493,23 @@ 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 Number) {
+ return format(((Number) o).doubleValue());
} else if (o instanceof Boolean) {
return format(((Boolean) o) ? 1 : 0);
}
return null;
}
- private String prefix(String... components) {
- return MetricRegistry.name(prefix, components);
+ private String prefix(String name) {
+ return MetricRegistry.name(prefix, name);
+ }
+
+ private String appendMetricAttribute(String name, String metricAttribute){
+ if (addMetricAttributesAsTags){
+ return name + ";metricattribute=" + metricAttribute;
+ }
+ return name + "." + metricAttribute;
}
private String format(long n) {
@@ -378,8 +517,6 @@ public class GraphiteReporter extends ScheduledReporter {
}
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);
+ return floatingPointFormatter.apply(v);
}
}
diff --git a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteSender.java b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteSender.java
index 84fe5fe..a8901c3 100644
--- a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteSender.java
+++ b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteSender.java
@@ -3,44 +3,43 @@ package com.codahale.metrics.graphite;
import java.io.Closeable;
import java.io.IOException;
-public interface GraphiteSender extends Closeable{
-
- /**
- * Connects to the server.
- *
- * @throws IllegalStateException if the client is already connected
- * @throws IOException if there is an error connecting
- */
- public void connect() throws IllegalStateException, IOException;
-
- /**
- * Sends the given measurement to the server.
- *
- * @param name the name of the metric
- * @param value the value of the metric
- * @param timestamp the timestamp of the metric
- * @throws IOException if there was an error sending the metric
- */
- public void send(String name, String value, long timestamp)
- throws IOException;
-
- /**
- * Flushes buffer, if applicable
- *
- * @throws IOException
- */
- void flush() throws IOException;
-
- /**
- * Returns true if ready to send data
- */
- boolean isConnected();
-
- /**
- * Returns the number of failed writes to the server.
- *
- * @return the number of failed writes to the server
- */
- public int getFailures();
+public interface GraphiteSender extends Closeable {
+
+ /**
+ * Connects to the server.
+ *
+ * @throws IllegalStateException if the client is already connected
+ * @throws IOException if there is an error connecting
+ */
+ void connect() throws IllegalStateException, IOException;
+
+ /**
+ * Sends the given measurement to the server.
+ *
+ * @param name the name of the metric
+ * @param value the value of the metric
+ * @param timestamp the timestamp of the metric
+ * @throws IOException if there was an error sending the metric
+ */
+ void send(String name, String value, long timestamp) throws IOException;
+
+ /**
+ * Flushes buffer, if applicable
+ *
+ * @throws IOException if there was an error during flushing metrics to the socket
+ */
+ void flush() throws IOException;
+
+ /**
+ * Returns true if ready to send data
+ */
+ boolean isConnected();
+
+ /**
+ * Returns the number of failed writes to the server.
+ *
+ * @return the number of failed writes to the server
+ */
+ int getFailures();
}
\ No newline at end of file
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 8e44a8e..bd49426 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
@@ -2,17 +2,17 @@ package com.codahale.metrics.graphite;
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
-import java.nio.charset.Charset;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
/**
* A client to a Carbon server using unconnected UDP
*/
public class GraphiteUDP implements GraphiteSender {
- private static final Charset UTF_8 = Charset.forName("UTF-8");
-
private final String hostname;
private final int port;
private InetSocketAddress address;
@@ -24,7 +24,7 @@ public class GraphiteUDP implements GraphiteSender {
* Creates a new client which sends data to given address using UDP
*
* @param hostname The hostname of the Carbon server
- * @param port The port of the Carbon server
+ * @param port The port of the Carbon server
*/
public GraphiteUDP(String hostname, int port) {
this.hostname = hostname;
@@ -51,7 +51,7 @@ public class GraphiteUDP implements GraphiteSender {
// Resolve hostname
if (hostname != null) {
- address = new InetSocketAddress(hostname, port);
+ address = new InetSocketAddress(InetAddress.getByName(hostname), port);
}
datagramChannel = DatagramChannel.open();
@@ -59,20 +59,13 @@ public class GraphiteUDP implements GraphiteSender {
@Override
public boolean isConnected() {
- return datagramChannel != null && !datagramChannel.socket().isClosed();
+ return datagramChannel != null && !datagramChannel.socket().isClosed();
}
@Override
public void send(String name, String value, long timestamp) throws IOException {
try {
- StringBuilder buf = new StringBuilder();
- buf.append(sanitize(name));
- buf.append(' ');
- buf.append(sanitize(value));
- buf.append(' ');
- buf.append(Long.toString(timestamp));
- buf.append('\n');
- String str = buf.toString();
+ String str = sanitize(name) + ' ' + sanitize(value) + ' ' + Long.toString(timestamp) + '\n';
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes(UTF_8));
datagramChannel.send(byteBuffer, address);
this.failures = 0;
@@ -89,7 +82,7 @@ public class GraphiteUDP implements GraphiteSender {
@Override
public void flush() throws IOException {
- // Nothing to do
+ // Nothing to do
}
@Override
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 4d8fa29..cc43fed 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
@@ -16,22 +16,48 @@ import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
/**
* 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 Charset UTF_8 = Charset.forName("UTF-8");
+ static class MetricTuple {
+ String name;
+ long timestamp;
+ String value;
+
+ MetricTuple(String name, long timestamp, String value) {
+ this.name = name;
+ this.timestamp = timestamp;
+ this.value = value;
+ }
+ }
+
+ /**
+ * Minimally necessary pickle opcodes.
+ */
+ private static final char
+ MARK = '(',
+ STOP = '.',
+ LONG = 'L',
+ STRING = 'S',
+ APPEND = 'a',
+ LIST = 'l',
+ TUPLE = 't',
+ QUOTE = '\'',
+ LF = '\n';
private static final Logger LOGGER = LoggerFactory.getLogger(PickledGraphite.class);
private final static int DEFAULT_BATCH_SIZE = 100;
private int batchSize;
// graphite expects a python-pickled list of nested tuples.
- private List<MetricTuple> metrics = new LinkedList<MetricTuple>();
+ private List<MetricTuple> metrics = new ArrayList<>();
private final String hostname;
private final int port;
@@ -47,8 +73,7 @@ public class PickledGraphite implements GraphiteSender {
* Creates a new client which connects to the given address using the default {@link SocketFactory}. This defaults
* to a batchSize of 100
*
- * @param address
- * the address of the Carbon server
+ * @param address the address of the Carbon server
*/
public PickledGraphite(InetSocketAddress address) {
this(address, DEFAULT_BATCH_SIZE);
@@ -57,10 +82,8 @@ public class PickledGraphite implements GraphiteSender {
/**
* Creates a new client which connects to the given address using the default {@link SocketFactory}.
*
- * @param address
- * the address of the Carbon server
- * @param batchSize
- * how many metrics are bundled into a single pickle request to graphite
+ * @param address the address of the Carbon server
+ * @param batchSize how many metrics are bundled into a single pickle request to graphite
*/
public PickledGraphite(InetSocketAddress address, int batchSize) {
this(address, SocketFactory.getDefault(), batchSize);
@@ -69,12 +92,9 @@ public class PickledGraphite implements GraphiteSender {
/**
* Creates a new client which connects to the given address and socket factory.
*
- * @param address
- * the address of the Carbon server
- * @param socketFactory
- * the socket factory
- * @param batchSize
- * how many metrics are bundled into a single pickle request to graphite
+ * @param address the address of the Carbon server
+ * @param socketFactory the socket factory
+ * @param batchSize how many metrics are bundled into a single pickle request to graphite
*/
public PickledGraphite(InetSocketAddress address, SocketFactory socketFactory, int batchSize) {
this(address, socketFactory, UTF_8, batchSize);
@@ -83,14 +103,10 @@ public class PickledGraphite implements GraphiteSender {
/**
* Creates a new client which connects to the given address and socket factory using the given character set.
*
- * @param address
- * the address of the Carbon server
- * @param socketFactory
- * the socket factory
- * @param charset
- * the character set used by the server
- * @param batchSize
- * how many metrics are bundled into a single pickle request to graphite
+ * @param address the address of the Carbon server
+ * @param socketFactory the socket factory
+ * @param charset the character set used by the server
+ * @param batchSize how many metrics are bundled into a single pickle request to graphite
*/
public PickledGraphite(InetSocketAddress address, SocketFactory socketFactory, Charset charset, int batchSize) {
this.address = address;
@@ -105,10 +121,8 @@ public class PickledGraphite implements GraphiteSender {
* Creates a new client which connects to the given address using the default {@link SocketFactory}. This defaults
* to a batchSize of 100
*
- * @param hostname
- * the hostname of the Carbon server
- * @param port
- * the port of the Carbon server
+ * @param hostname the hostname of the Carbon server
+ * @param port the port of the Carbon server
*/
public PickledGraphite(String hostname, int port) {
this(hostname, port, DEFAULT_BATCH_SIZE);
@@ -117,12 +131,9 @@ public class PickledGraphite implements GraphiteSender {
/**
* Creates a new client which connects to the given address using the default {@link SocketFactory}.
*
- * @param hostname
- * the hostname of the Carbon server
- * @param port
- * the port of the Carbon server
- * @param batchSize
- * how many metrics are bundled into a single pickle request to graphite
+ * @param hostname the hostname of the Carbon server
+ * @param port the port of the Carbon server
+ * @param batchSize how many metrics are bundled into a single pickle request to graphite
*/
public PickledGraphite(String hostname, int port, int batchSize) {
this(hostname, port, SocketFactory.getDefault(), batchSize);
@@ -131,14 +142,10 @@ public class PickledGraphite implements GraphiteSender {
/**
* Creates a new client which connects to the given address and socket factory.
*
- * @param hostname
- * the hostname of the Carbon server
- * @param port
- * the port of the Carbon server
- * @param socketFactory
- * the socket factory
- * @param batchSize
- * how many metrics are bundled into a single pickle request to graphite
+ * @param hostname the hostname of the Carbon server
+ * @param port the port of the Carbon server
+ * @param socketFactory the socket factory
+ * @param batchSize how many metrics are bundled into a single pickle request to graphite
*/
public PickledGraphite(String hostname, int port, SocketFactory socketFactory, int batchSize) {
this(hostname, port, socketFactory, UTF_8, batchSize);
@@ -147,16 +154,11 @@ public class PickledGraphite implements GraphiteSender {
/**
* Creates a new client which connects to the given address and socket factory using the given character set.
*
- * @param hostname
- * the hostname of the Carbon server
- * @param port
- * the port of the Carbon server
- * @param socketFactory
- * the socket factory
- * @param charset
- * the character set used by the server
- * @param batchSize
- * how many metrics are bundled into a single pickle request to graphite
+ * @param hostname the hostname of the Carbon server
+ * @param port the port of the Carbon server
+ * @param socketFactory the socket factory
+ * @param charset the character set used by the server
+ * @param batchSize how many metrics are bundled into a single pickle request to graphite
*/
public PickledGraphite(String hostname, int port, SocketFactory socketFactory, Charset charset, int batchSize) {
this.address = null;
@@ -191,19 +193,15 @@ public class PickledGraphite implements GraphiteSender {
/**
* Convert the metric to a python tuple of the form:
- * <p/>
+ * <p>
* (timestamp, (name, value))
- * <p/>
+ * <p>
* And add it to the list of metrics. If we reach the batch size, write them out.
*
- * @param name
- * the name of the metric
- * @param value
- * the value of the metric
- * @param timestamp
- * the timestamp of the metric
- * @throws IOException
- * if there was an error sending the metric
+ * @param name the name of the metric
+ * @param value the value of the metric
+ * @param timestamp the timestamp of the metric
+ * @throws IOException if there was an error sending the metric
*/
@Override
public void send(String name, String value, long timestamp) throws IOException {
@@ -276,27 +274,14 @@ public class PickledGraphite implements GraphiteSender {
}
}
- /**
- * Minimally necessary pickle opcodes.
- */
- private final char
- MARK = '(',
- STOP = '.',
- LONG = 'L',
- STRING = 'S',
- APPEND = 'a',
- LIST = 'l',
- TUPLE = 't',
- QUOTE = '\'',
- LF = '\n';
-
/**
* See: http://readthedocs.org/docs/graphite/en/1.0/feeding-carbon.html
*
- * @throws IOException
+ * @throws IOException shouldn't happen because we write to memory.
*/
byte[] pickleMetrics(List<MetricTuple> metrics) throws IOException {
- ByteArrayOutputStream out = new ByteArrayOutputStream(metrics.size() * 75); // Extremely rough estimate of 75 bytes per message
+ // Extremely rough estimate of 75 bytes per message
+ ByteArrayOutputStream out = new ByteArrayOutputStream(metrics.size() * 75);
Writer pickled = new OutputStreamWriter(out, charset);
pickled.append(MARK);
@@ -345,18 +330,6 @@ public class PickledGraphite implements GraphiteSender {
return out.toByteArray();
}
- static class MetricTuple {
- String name;
- long timestamp;
- String value;
-
- MetricTuple(String name, long timestamp, String value) {
- this.name = name;
- this.timestamp = timestamp;
- this.value = value;
- }
- }
-
protected String sanitize(String s) {
return GraphiteSanitize.sanitize(s);
}
diff --git a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteRabbitMQTest.java b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteRabbitMQTest.java
index 0ac5c16..3b665b7 100755
--- a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteRabbitMQTest.java
+++ b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteRabbitMQTest.java
@@ -1,6 +1,5 @@
package com.codahale.metrics.graphite;
-import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
@@ -9,16 +8,20 @@ import org.junit.Test;
import java.io.IOException;
import java.net.UnknownHostException;
-import java.nio.charset.Charset;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;
-
-
-import static org.mockito.Mockito.*;
-
-public class GraphiteRabbitMQTest
-{
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class GraphiteRabbitMQTest {
private final ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
private final Connection connection = mock(Connection.class);
private final Channel channel = mock(Channel.class);
@@ -29,8 +32,6 @@ public class GraphiteRabbitMQTest
private final GraphiteRabbitMQ graphite = new GraphiteRabbitMQ(connectionFactory, "graphite");
- private static final Charset UTF_8 = Charset.forName("UTF-8");
-
@Before
public void setUp() throws Exception {
when(connectionFactory.newConnection()).thenReturn(connection);
@@ -40,8 +41,8 @@ public class GraphiteRabbitMQTest
when(bogusConnectionFactory.newConnection()).thenReturn(bogusConnection);
when(bogusConnection.createChannel()).thenReturn(bogusChannel);
doThrow(new IOException())
- .when(bogusChannel)
- .basicPublish(anyString(), anyString(), any(AMQP.BasicProperties.class), any(byte[].class));
+ .when(bogusChannel)
+ .basicPublish(anyString(), anyString(), any(), any(byte[].class));
}
@Test
@@ -55,14 +56,14 @@ public class GraphiteRabbitMQTest
@Test
public void measuresFailures() throws Exception {
- final GraphiteRabbitMQ graphite = new GraphiteRabbitMQ(bogusConnectionFactory, "graphite");
- graphite.connect();
-
- try {
- graphite.send("name", "value", 0);
- failBecauseExceptionWasNotThrown(IOException.class);
- } catch (IOException e) {
- assertThat(graphite.getFailures()).isEqualTo(1);
+ try (final GraphiteRabbitMQ graphite = new GraphiteRabbitMQ(bogusConnectionFactory, "graphite")) {
+ graphite.connect();
+ try {
+ graphite.send("name", "value", 0);
+ failBecauseExceptionWasNotThrown(IOException.class);
+ } catch (IOException e) {
+ assertThat(graphite.getFailures()).isEqualTo(1);
+ }
}
}
@@ -82,7 +83,7 @@ public class GraphiteRabbitMQTest
failBecauseExceptionWasNotThrown(IllegalStateException.class);
} catch (IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("Already connected");
- }
+ }
}
@Test
@@ -92,7 +93,8 @@ public class GraphiteRabbitMQTest
String expectedMessage = "name value 100\n";
- verify(channel, times(1)).basicPublish("graphite", "name", null, expectedMessage.getBytes(UTF_8));
+ verify(channel, times(1)).basicPublish("graphite", "name", null,
+ expectedMessage.getBytes(UTF_8));
assertThat(graphite.getFailures()).isZero();
}
@@ -104,24 +106,22 @@ public class GraphiteRabbitMQTest
String expectedMessage = "name-to-sanitize value-to-sanitize 100\n";
- verify(channel, times(1)).basicPublish("graphite", "name-to-sanitize", null, expectedMessage.getBytes(UTF_8));
+ verify(channel, times(1)).basicPublish("graphite", "name-to-sanitize", null,
+ expectedMessage.getBytes(UTF_8));
assertThat(graphite.getFailures()).isZero();
}
@Test
- public void shouldFailWhenGraphiteHostUnavailable() throws Exception {
+ public void shouldFailWhenGraphiteHostUnavailable() {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("some-unknown-host");
- GraphiteRabbitMQ unavailableGraphite = new GraphiteRabbitMQ(connectionFactory, "graphite");
-
- try {
+ try (GraphiteRabbitMQ unavailableGraphite = new GraphiteRabbitMQ(connectionFactory, "graphite")) {
unavailableGraphite.connect();
failBecauseExceptionWasNotThrown(UnknownHostException.class);
} catch (Exception e) {
- assertThat(e.getMessage())
- .isEqualTo("some-unknown-host");
+ assertThat(e.getMessage()).contains("some-unknown-host");
}
}
}
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 a4a43f5..3498b2c 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,21 +1,37 @@
package com.codahale.metrics.graphite;
-import com.codahale.metrics.*;
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricAttribute;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Snapshot;
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.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
import java.util.Collections;
import java.util.EnumSet;
+import java.util.Locale;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
public class GraphiteReporterTest {
private final long timestamp = 1000198;
@@ -23,36 +39,36 @@ public class GraphiteReporterTest {
private final Graphite graphite = mock(Graphite.class);
private final MetricRegistry registry = mock(MetricRegistry.class);
private final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry)
- .withClock(clock)
- .prefixedWith("prefix")
- .convertRatesTo(TimeUnit.SECONDS)
- .convertDurationsTo(TimeUnit.MILLISECONDS)
- .filter(MetricFilter.ALL)
- .disabledMetricAttributes(Collections.<MetricAttribute>emptySet())
- .build(graphite);
+ .withClock(clock)
+ .prefixedWith("prefix")
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .filter(MetricFilter.ALL)
+ .disabledMetricAttributes(Collections.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);
+ .forRegistry(registry)
+ .withClock(clock)
+ .prefixedWith("prefix")
+ .convertRatesTo(TimeUnit.MINUTES)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .filter(MetricFilter.ALL)
+ .disabledMetricAttributes(Collections.emptySet())
+ .build(graphite);
@Before
- public void setUp() throws Exception {
+ public void setUp() {
when(clock.getTime()).thenReturn(timestamp * 1000);
}
@Test
public void doesNotReportStringGaugeValues() throws Exception {
reporter.report(map("gauge", gauge("value")),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -66,10 +82,10 @@ public class GraphiteReporterTest {
@Test
public void reportsByteGaugeValues() throws Exception {
reporter.report(map("gauge", gauge((byte) 1)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -83,10 +99,10 @@ public class GraphiteReporterTest {
@Test
public void reportsShortGaugeValues() throws Exception {
reporter.report(map("gauge", gauge((short) 1)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -100,10 +116,10 @@ public class GraphiteReporterTest {
@Test
public void reportsIntegerGaugeValues() throws Exception {
reporter.report(map("gauge", gauge(1)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -117,10 +133,10 @@ public class GraphiteReporterTest {
@Test
public void reportsLongGaugeValues() throws Exception {
reporter.report(map("gauge", gauge(1L)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -134,10 +150,10 @@ public class GraphiteReporterTest {
@Test
public void reportsFloatGaugeValues() throws Exception {
reporter.report(map("gauge", gauge(1.1f)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -151,10 +167,10 @@ public class GraphiteReporterTest {
@Test
public void reportsDoubleGaugeValues() throws Exception {
reporter.report(map("gauge", gauge(1.1)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -167,42 +183,61 @@ public class GraphiteReporterTest {
@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());
+ try (final GraphiteReporter graphiteReporter = getReporterWithCustomFormat()) {
+ reportGaugeValue(graphiteReporter, 1.13574);
+ verifyGraphiteSentCorrectMetricValue("prefix.gauge", "1.1357", timestamp);
+ verifyNoMoreInteractions(graphite);
+ }
+ }
+
+ @Test
+ public void reportDoubleGaugeValuesUsingCustomFormatter() throws Exception {
+ DecimalFormat formatter = new DecimalFormat("##.##########", DecimalFormatSymbols.getInstance(Locale.US));
+
+ try (GraphiteReporter graphiteReporter = GraphiteReporter.forRegistry(registry)
+ .withClock(clock)
+ .prefixedWith("prefix")
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .filter(MetricFilter.ALL)
+ .disabledMetricAttributes(Collections.emptySet())
+ .withFloatingPointFormatter(formatter::format)
+ .build(graphite)) {
+ reportGaugeValue(graphiteReporter, 0.000045322);
+ verifyGraphiteSentCorrectMetricValue("prefix.gauge", "0.000045322", timestamp);
+ verifyNoMoreInteractions(graphite);
+ }
+ }
+ private void reportGaugeValue(GraphiteReporter graphiteReporter, double value) {
+ graphiteReporter.report(map("gauge", gauge(value)),
+ map(),
+ map(),
+ map(),
+ map());
+ }
+
+ private void verifyGraphiteSentCorrectMetricValue(String metricName, String value, long timestamp) throws Exception {
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
- inOrder.verify(graphite).send("prefix.gauge", "1.1357", timestamp);
+ inOrder.verify(graphite).send(metricName, value, 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());
+ map(),
+ map(),
+ map(),
+ map());
reporter.report(map("gauge", gauge(false)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
inOrder.verify(graphite).send("prefix.gauge", "1", timestamp);
@@ -221,11 +256,11 @@ public class GraphiteReporterTest {
final Counter counter = mock(Counter.class);
when(counter.getCount()).thenReturn(100L);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map("counter", counter),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ reporter.report(map(),
+ map("counter", counter),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -255,11 +290,11 @@ public class GraphiteReporterTest {
when(histogram.getSnapshot()).thenReturn(snapshot);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map("histogram", histogram),
- this.<Meter>map(),
- this.<Timer>map());
+ reporter.report(map(),
+ map(),
+ map("histogram", histogram),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -289,11 +324,11 @@ public class GraphiteReporterTest {
when(meter.getFifteenMinuteRate()).thenReturn(4.0);
when(meter.getMeanRate()).thenReturn(5.0);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map("meter", meter),
- this.<Timer>map());
+ reporter.report(map(),
+ map(),
+ map(),
+ map("meter", meter),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -317,11 +352,11 @@ public class GraphiteReporterTest {
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());
+ minuteRateReporter.report(this.map(),
+ this.map(),
+ this.map(),
+ this.map("meter", meter),
+ this.map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -356,15 +391,15 @@ public class GraphiteReporterTest {
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));
+ .toNanos(1000));
when(timer.getSnapshot()).thenReturn(snapshot);
- reporter.report(this.<Gauge>map(),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- map("timer", timer));
+ reporter.report(map(),
+ map(),
+ map(),
+ map(),
+ map("timer", timer));
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -395,10 +430,10 @@ public class GraphiteReporterTest {
public void closesConnectionIfGraphiteIsUnavailable() throws Exception {
doThrow(new UnknownHostException("UNKNOWN-HOST")).when(graphite).connect();
reporter.report(map("gauge", gauge(1)),
- this.<Counter>map(),
- this.<Histogram>map(),
- this.<Meter>map(),
- this.<Timer>map());
+ map(),
+ map(),
+ map(),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -412,7 +447,10 @@ public class GraphiteReporterTest {
public void closesConnectionOnReporterStop() throws Exception {
reporter.stop();
- verify(graphite).close();
+ final InOrder inOrder = inOrder(graphite);
+ inOrder.verify(graphite).connect();
+ inOrder.verify(graphite).flush();
+ inOrder.verify(graphite, times(2)).close();
verifyNoMoreInteractions(graphite);
}
@@ -431,18 +469,18 @@ public class GraphiteReporterTest {
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());
+ .withClock(clock)
+ .prefixedWith("prefix")
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .filter(MetricFilter.ALL)
+ .disabledMetricAttributes(disabledMetricAttributes)
+ .build(graphite);
+ reporterWithdisabledMetricAttributes.report(map(),
+ map("counter", counter),
+ map(),
+ map("meter", meter),
+ map());
final InOrder inOrder = inOrder(graphite);
inOrder.verify(graphite).connect();
@@ -456,20 +494,61 @@ public class GraphiteReporterTest {
verifyNoMoreInteractions(graphite);
}
+ @Test
+ public void sendsMetricAttributesAsTagsIfEnabled() throws Exception {
+ final Counter counter = mock(Counter.class);
+ when(counter.getCount()).thenReturn(100L);
+
+ getReporterThatSendsMetricAttributesAsTags().report(map(),
+ map("counter", counter),
+ map(),
+ map(),
+ map());
+
+ final InOrder inOrder = inOrder(graphite);
+ inOrder.verify(graphite).connect();
+ inOrder.verify(graphite).send("prefix.counter;metricattribute=count", "100", timestamp);
+ inOrder.verify(graphite).flush();
+ inOrder.verify(graphite).close();
+
+ verifyNoMoreInteractions(graphite);
+ }
+
+ private GraphiteReporter getReporterWithCustomFormat() {
+ return new GraphiteReporter(registry, graphite, clock, "prefix",
+ TimeUnit.SECONDS, TimeUnit.MICROSECONDS, MetricFilter.ALL, null, false,
+ Collections.emptySet(), false) {
+ @Override
+ protected String format(double v) {
+ return String.format(Locale.US, "%4.4f", v);
+ }
+ };
+ }
+
+
+ private GraphiteReporter getReporterThatSendsMetricAttributesAsTags() {
+ return GraphiteReporter.forRegistry(registry)
+ .withClock(clock)
+ .prefixedWith("prefix")
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .filter(MetricFilter.ALL)
+ .disabledMetricAttributes(Collections.emptySet())
+ .addMetricAttributesAsTags(true)
+ .build(graphite);
+ }
private <T> SortedMap<String, T> map() {
- return new TreeMap<String, T>();
+ return new TreeMap<>();
}
private <T> SortedMap<String, T> map(String name, T metric) {
- final TreeMap<String, T> map = new TreeMap<String, T>();
+ final TreeMap<String, T> map = new TreeMap<>();
map.put(name, metric);
return map;
}
- private <T> Gauge gauge(T value) {
- final Gauge gauge = mock(Gauge.class);
- when(gauge.getValue()).thenReturn(value);
- return gauge;
+ private <T> Gauge<T> gauge(T value) {
+ return () -> value;
}
}
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 b3337ce..794ca74 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
@@ -2,12 +2,10 @@ package com.codahale.metrics.graphite;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import javax.net.SocketFactory;
-
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
@@ -15,10 +13,16 @@ import java.net.UnknownHostException;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.*;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class GraphiteTest {
private final String host = "example.com";
@@ -27,48 +31,30 @@ public class GraphiteTest {
private final InetSocketAddress address = new InetSocketAddress(host, port);
private final Socket socket = mock(Socket.class);
- private final ByteArrayOutputStream output = spy(new ByteArrayOutputStream());
-
- private Graphite graphite;
+ private final ByteArrayOutputStream output = spy(ByteArrayOutputStream.class);
@Before
public void setUp() throws Exception {
final AtomicBoolean connected = new AtomicBoolean(true);
final AtomicBoolean closed = new AtomicBoolean(false);
- when(socket.isConnected()).thenAnswer(new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- return connected.get();
- }
- });
-
- when(socket.isClosed()).thenAnswer(new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- return closed.get();
- }
- });
-
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- connected.set(false);
- closed.set(true);
- return null;
- }
+ when(socket.isConnected()).thenAnswer(invocation -> connected.get());
+
+ when(socket.isClosed()).thenAnswer(invocation -> closed.get());
+
+ doAnswer(invocation -> {
+ connected.set(false);
+ closed.set(true);
+ return null;
}).when(socket).close();
when(socket.getOutputStream()).thenReturn(output);
// Mock behavior of socket.getOutputStream().close() calling socket.close();
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- invocation.callRealMethod();
- socket.close();
- return null;
- }
+ doAnswer(invocation -> {
+ invocation.callRealMethod();
+ socket.close();
+ return null;
}).when(output).close();
when(socketFactory.createSocket(any(InetAddress.class), anyInt())).thenReturn(socket);
@@ -76,94 +62,82 @@ public class GraphiteTest {
@Test
public void connectsToGraphiteWithInetSocketAddress() throws Exception {
- graphite = new Graphite(address, socketFactory);
- graphite.connect();
-
+ try (Graphite graphite = new Graphite(address, socketFactory)) {
+ graphite.connect();
+ }
verify(socketFactory).createSocket(address.getAddress(), address.getPort());
}
@Test
public void connectsToGraphiteWithHostAndPort() throws Exception {
- graphite = new Graphite(host, port, socketFactory);
- graphite.connect();
-
+ try (Graphite graphite = new Graphite(host, port, socketFactory)) {
+ graphite.connect();
+ }
verify(socketFactory).createSocket(address.getAddress(), port);
}
@Test
- public void measuresFailures() throws Exception {
- graphite = new Graphite(address, socketFactory);
- assertThat(graphite.getFailures())
- .isZero();
+ public void measuresFailures() throws IOException {
+ try (Graphite graphite = new Graphite(address, socketFactory)) {
+ assertThat(graphite.getFailures()).isZero();
+ }
}
@Test
public void disconnectsFromGraphite() throws Exception {
- graphite = new Graphite(address, socketFactory);
- graphite.connect();
- graphite.close();
+ try (Graphite graphite = new Graphite(address, socketFactory)) {
+ graphite.connect();
+ }
verify(socket, times(2)).close();
}
@Test
public void doesNotAllowDoubleConnections() throws Exception {
- graphite = new Graphite(address, socketFactory);
- graphite.connect();
- try {
- graphite.connect();
- failBecauseExceptionWasNotThrown(IllegalStateException.class);
- } catch (IllegalStateException e) {
- assertThat(e.getMessage())
- .isEqualTo("Already connected");
+ try (Graphite graphite = new Graphite(address, socketFactory)) {
+ assertThatNoException().isThrownBy(graphite::connect);
+ assertThatThrownBy(graphite::connect)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Already connected");
}
}
@Test
public void writesValuesToGraphite() throws Exception {
- graphite = new Graphite(address, socketFactory);
- graphite.connect();
- graphite.send("name", "value", 100);
- graphite.close();
-
- assertThat(output.toString())
- .isEqualTo("name value 100\n");
+ try (Graphite graphite = new Graphite(address, socketFactory)) {
+ graphite.connect();
+ graphite.send("name", "value", 100);
+ }
+ assertThat(output).hasToString("name value 100\n");
}
@Test
public void sanitizesNames() throws Exception {
- graphite = new Graphite(address, socketFactory);
- graphite.connect();
- graphite.send("name woo", "value", 100);
- graphite.close();
-
- assertThat(output.toString())
- .isEqualTo("name-woo value 100\n");
+ try (Graphite graphite = new Graphite(address, socketFactory)) {
+ graphite.connect();
+ graphite.send("name woo", "value", 100);
+ }
+ assertThat(output).hasToString("name-woo value 100\n");
}
@Test
public void sanitizesValues() throws Exception {
- graphite = new Graphite(address, socketFactory);
- graphite.connect();
- graphite.send("name", "value woo", 100);
- graphite.close();
-
- assertThat(output.toString())
- .isEqualTo("name value-woo 100\n");
+ try (Graphite graphite = new Graphite(address, socketFactory)) {
+ graphite.connect();
+ graphite.send("name", "value woo", 100);
+ }
+ assertThat(output).hasToString("name value-woo 100\n");
}
@Test
- public void notifiesIfGraphiteIsUnavailable() throws Exception {
+ public void notifiesIfGraphiteIsUnavailable() throws IOException {
final String unavailableHost = "unknown-host-10el6m7yg56ge7dmcom";
InetSocketAddress unavailableAddress = new InetSocketAddress(unavailableHost, 1234);
- Graphite unavailableGraphite = new Graphite(unavailableAddress, socketFactory);
-
- try {
- unavailableGraphite.connect();
- failBecauseExceptionWasNotThrown(UnknownHostException.class);
- } catch (Exception e) {
- assertThat(e.getMessage())
- .isEqualTo(unavailableHost);
+
+ try (Graphite unavailableGraphite = new Graphite(unavailableAddress, socketFactory)) {
+ assertThatThrownBy(unavailableGraphite::connect)
+ .isInstanceOf(UnknownHostException.class)
+ .hasMessage(unavailableHost);
}
}
}
diff --git a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/PickledGraphiteTest.java b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/PickledGraphiteTest.java
index ddee924..c08a793 100644
--- a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/PickledGraphiteTest.java
+++ b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/PickledGraphiteTest.java
@@ -1,15 +1,7 @@
package com.codahale.metrics.graphite;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.*;
-
import org.junit.Before;
import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import org.python.core.PyList;
import org.python.core.PyTuple;
@@ -20,32 +12,33 @@ import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleBindings;
-
import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicBoolean;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
public class PickledGraphiteTest {
private final SocketFactory socketFactory = mock(SocketFactory.class);
private final InetSocketAddress address = new InetSocketAddress("example.com", 1234);
- private final PickledGraphite graphite = new PickledGraphite(address, socketFactory, Charset.forName("UTF-8"), 2);
+ private final PickledGraphite graphite = new PickledGraphite(address, socketFactory, UTF_8, 2);
private final Socket socket = mock(Socket.class);
- private final ByteArrayOutputStream output = spy(new ByteArrayOutputStream());
-
- // Pulls apart the pickled payload. This skips ahead 4 characters to safely ignore
- // the header (length)
- private static final String UNPICKLER_SCRIPT =
- "import cPickle\n" +
- "import struct\n" +
- "format = '!L'\n" +
- "headerLength = struct.calcsize(format)\n" +
- "payloadLength, = struct.unpack(format, payload[:headerLength])\n" +
- "batchLength = headerLength + payloadLength.intValue()\n" +
- "metrics = cPickle.loads(payload[headerLength:batchLength])\n";
+ private final ByteArrayOutputStream output = spy(ByteArrayOutputStream.class);
private CompiledScript unpickleScript;
@@ -54,39 +47,23 @@ public class PickledGraphiteTest {
final AtomicBoolean connected = new AtomicBoolean(true);
final AtomicBoolean closed = new AtomicBoolean(false);
- when(socket.isConnected()).thenAnswer(new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- return connected.get();
- }
- });
-
- when(socket.isClosed()).thenAnswer(new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- return closed.get();
- }
- });
-
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- connected.set(false);
- closed.set(true);
- return null;
- }
+ when(socket.isConnected()).thenAnswer(invocation -> connected.get());
+
+ when(socket.isClosed()).thenAnswer(invocation -> closed.get());
+
+ doAnswer(invocation -> {
+ connected.set(false);
+ closed.set(true);
+ return null;
}).when(socket).close();
when(socket.getOutputStream()).thenReturn(output);
// Mock behavior of socket.getOutputStream().close() calling socket.close();
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- invocation.callRealMethod();
- socket.close();
- return null;
- }
+ doAnswer(invocation -> {
+ invocation.callRealMethod();
+ socket.close();
+ return null;
}).when(output).close();
when(socketFactory.createSocket(any(InetAddress.class),
@@ -94,7 +71,9 @@ public class PickledGraphiteTest {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("python");
Compilable compilable = (Compilable) engine;
- unpickleScript = compilable.compile(UNPICKLER_SCRIPT);
+ try (InputStream is = PickledGraphiteTest.class.getResource("/upickle.py").openStream()) {
+ unpickleScript = compilable.compile(new InputStreamReader(is, UTF_8));
+ }
}
@Test
@@ -170,7 +149,7 @@ public class PickledGraphiteTest {
}
}
- String unpickleOutput() throws Exception {
+ private String unpickleOutput() throws Exception {
StringBuilder results = new StringBuilder();
// the charset is important. if the GraphitePickleReporter and this test
@@ -184,7 +163,7 @@ public class PickledGraphiteTest {
bindings.put("payload", payload.substring(nextIndex));
unpickleScript.eval(bindings);
result.addAll(result.size(), (PyList) bindings.get("metrics"));
- nextIndex += (Integer) bindings.get("batchLength");
+ nextIndex += ((BigInteger) bindings.get("batchLength")).intValue();
}
for (Object aResult : result) {
diff --git a/metrics-graphite/src/test/resources/upickle.py b/metrics-graphite/src/test/resources/upickle.py
new file mode 100644
index 0000000..9d51d21
--- /dev/null
+++ b/metrics-graphite/src/test/resources/upickle.py
@@ -0,0 +1,9 @@
+# Pulls apart the pickled payload. This skips ahead 4 characters to safely ignore
+# the header (length)
+import cPickle
+import struct
+format = '!L'
+headerLength = struct.calcsize(format)
+payloadLength, = struct.unpack(format, payload[:headerLength])
+batchLength = headerLength + payloadLength
+metrics = cPickle.loads(payload[headerLength:batchLength])
diff --git a/metrics-healthchecks/pom.xml b/metrics-healthchecks/pom.xml
index c1a64c2..abdf6d9 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-healthchecks</artifactId>
@@ -15,13 +15,66 @@
An addition to Metrics which provides the ability to run application-specific health checks,
allowing you to check your application's heath in production.
</description>
-
+
+ <properties>
+ <javaModuleName>com.codahale.metrics.health</javaModuleName>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-jvm</artifactId>
- <version>${project.version}</version>
<optional>true</optional>
</dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
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
index aef57de..f7deb90 100644
--- a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/AsyncHealthCheckDecorator.java
+++ b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/AsyncHealthCheckDecorator.java
@@ -1,28 +1,34 @@
package com.codahale.metrics.health;
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.health.annotation.Async;
+
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 {
+public 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 final long healthyTtl;
+ private final Clock clock;
private volatile Result result;
- AsyncHealthCheckDecorator(HealthCheck healthCheck, ScheduledExecutorService executorService) {
+ AsyncHealthCheckDecorator(HealthCheck healthCheck, ScheduledExecutorService executorService, Clock clock) {
check(healthCheck != null, "healthCheck cannot be null");
check(executorService != null, "executorService cannot be null");
- Async async = (Async) healthCheck.getClass().getAnnotation(Async.class);
+ 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.clock = clock;
this.healthCheck = healthCheck;
+ this.healthyTtl = async.unit().toMillis(async.healthyTtl() <= 0 ? 2 * async.period() : async.healthyTtl());
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())) {
@@ -33,6 +39,10 @@ class AsyncHealthCheckDecorator extends HealthCheck implements Runnable {
}
+ AsyncHealthCheckDecorator(HealthCheck healthCheck, ScheduledExecutorService executorService) {
+ this(healthCheck, executorService, Clock.defaultClock());
+ }
+
@Override
public void run() {
result = healthCheck.execute();
@@ -40,6 +50,17 @@ class AsyncHealthCheckDecorator extends HealthCheck implements Runnable {
@Override
protected Result check() throws Exception {
+ long expiration = clock.getTime() - result.getTime() - healthyTtl;
+ if (expiration > 0) {
+ return Result.builder()
+ .unhealthy()
+ .usingClock(clock)
+ .withMessage("Result was %s but it expired %d milliseconds ago",
+ result.isHealthy() ? "healthy" : "unhealthy",
+ expiration)
+ .build();
+ }
+
return result;
}
@@ -47,14 +68,13 @@ class AsyncHealthCheckDecorator extends HealthCheck implements Runnable {
return future.cancel(true);
}
- HealthCheck getHealthCheck() {
+ public HealthCheck getHealthCheck() {
return healthCheck;
}
- private void check(boolean expression, String message) {
+ private static 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 f2baa81..e123efa 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,22 +1,28 @@
package com.codahale.metrics.health;
-import java.text.SimpleDateFormat;
+import com.codahale.metrics.Clock;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.Collections;
-import java.util.Date;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* 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 and optional details)
* or unhealthy (with either an error message or a thrown exception and optional details).
*/
public static class Result {
- private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
+ private static final DateTimeFormatter DATE_FORMAT_PATTERN =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
private static final int PRIME = 31;
/**
@@ -40,10 +46,10 @@ public abstract class HealthCheck {
/**
* Returns a healthy {@link Result} with a formatted message.
- * <p/>
+ * <p>
* Message formatting follows the same rules as {@link String#format(String, Object...)}.
*
- * @param message a message format
+ * @param message a message format\\
* @param args the arguments apply to the message format
* @return a healthy {@link Result} with an additional message
* @see String#format(String, Object...)
@@ -64,7 +70,7 @@ public abstract class HealthCheck {
/**
* Returns an unhealthy {@link Result} with a formatted message.
- * <p/>
+ * <p>
* Message formatting follows the same rules as {@link String#format(String, Object...)}.
*
* @param message a message format
@@ -87,7 +93,7 @@ public abstract class HealthCheck {
}
- /**
+ /**
* Returns a new {@link ResultBuilder}
*
* @return the {@link ResultBuilder}
@@ -100,22 +106,24 @@ public abstract class HealthCheck {
private final String message;
private final Throwable error;
private final Map<String, Object> details;
- private final String timestamp;
+ private final long time;
+
+ private long duration; // Calculated field
private Result(boolean isHealthy, String message, Throwable error) {
- this(isHealthy, message, error, null);
+ this(isHealthy, message, error, null, Clock.defaultClock());
}
private Result(ResultBuilder builder) {
- this(builder.healthy, builder.message, builder.error, builder.details);
+ this(builder.healthy, builder.message, builder.error, builder.details, builder.clock);
}
- private Result(boolean isHealthy, String message, Throwable error, Map<String, Object> details) {
+ private Result(boolean isHealthy, String message, Throwable error, Map<String, Object> details, Clock clock) {
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());
+ this.time = clock.getTime();
}
/**
@@ -148,11 +156,41 @@ public abstract class HealthCheck {
}
/**
- * Returns the timestamp when the result was created.
+ * Returns the timestamp when the result was created as a formatted String.
+ *
* @return a formatted timestamp
*/
public String getTimestamp() {
- return timestamp;
+ Instant currentInstant = Instant.ofEpochMilli(time);
+ ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(currentInstant, ZoneId.systemDefault());
+ return DATE_FORMAT_PATTERN.format(zonedDateTime);
+ }
+
+ /**
+ * Returns the time when the result was created, in milliseconds since Epoch
+ *
+ * @return the time when the result was created
+ */
+ public long getTime() {
+ return time;
+ }
+
+ /**
+ * Returns the duration in milliseconds that the healthcheck took to run
+ *
+ * @return the duration
+ */
+ public long getDuration() {
+ return duration;
+ }
+
+ /**
+ * Sets the duration in milliseconds. This will indicate the time it took to run the individual healthcheck
+ *
+ * @param duration The duration in milliseconds
+ */
+ public void setDuration(long duration) {
+ this.duration = duration;
}
public Map<String, Object> getDetails() {
@@ -171,15 +209,15 @@ public abstract class HealthCheck {
return healthy == result.healthy &&
!(error != null ? !error.equals(result.error) : result.error != null) &&
!(message != null ? !message.equals(result.message) : result.message != null) &&
- !(timestamp != null ? !timestamp.equals(result.timestamp) : result.timestamp != null);
+ time == result.time;
}
@Override
public int hashCode() {
- int result = (healthy ? 1 : 0);
+ 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);
+ result = PRIME * result + (Long.hashCode(time));
return result;
}
@@ -193,12 +231,11 @@ public abstract class HealthCheck {
if (error != null) {
builder.append(", error=").append(error);
}
- builder.append(", timestamp=").append(timestamp);
+ builder.append(", duration=").append(duration);
+ builder.append(", timestamp=").append(getTimestamp());
if (details != null) {
- Iterator<Map.Entry<String, Object>> it = details.entrySet().iterator();
- while (it.hasNext()) {
+ for (Map.Entry<String, Object> e : details.entrySet()) {
builder.append(", ");
- Map.Entry<String, Object> e = it.next();
builder.append(e.getKey())
.append("=")
.append(String.valueOf(e.getValue()));
@@ -218,16 +255,18 @@ public abstract class HealthCheck {
private String message;
private Throwable error;
private Map<String, Object> details;
+ private Clock clock;
protected ResultBuilder() {
this.healthy = true;
- this.details = new LinkedHashMap<String, Object>();
+ this.details = new LinkedHashMap<>();
+ this.clock = Clock.defaultClock();
}
/**
* Configure an healthy result
*
- * @return
+ * @return this builder with healthy status
*/
public ResultBuilder healthy() {
this.healthy = true;
@@ -237,7 +276,7 @@ public abstract class HealthCheck {
/**
* Configure an unhealthy result
*
- * @return
+ * @return this builder with unhealthy status
*/
public ResultBuilder unhealthy() {
this.healthy = false;
@@ -248,7 +287,7 @@ public abstract class HealthCheck {
* Configure an unhealthy result with an {@code error}
*
* @param error the error
- * @return
+ * @return this builder with the given error
*/
public ResultBuilder unhealthy(Throwable error) {
this.error = error;
@@ -268,7 +307,7 @@ public abstract class HealthCheck {
/**
* Set an optional formatted message
- * <p/>
+ * <p>
* Message formatting follows the same rules as {@link String#format(String, Object...)}.
*
* @param message a message format
@@ -289,12 +328,24 @@ public abstract class HealthCheck {
*/
public ResultBuilder withDetail(String key, Object data) {
if (this.details == null) {
- this.details = new LinkedHashMap<String, Object>();
+ this.details = new LinkedHashMap<>();
}
this.details.put(key, data);
return this;
}
+ /**
+ * Configure this {@link ResultBuilder} to use the given {@code clock} instead of the default clock.
+ * If not specified, the default clock is {@link Clock#defaultClock()}.
+ *
+ * @param clock the {@link Clock} to use when generating the health check timestamp (useful for unit testing)
+ * @return this builder configured to use the given {@code clock}
+ */
+ public ResultBuilder usingClock(Clock clock) {
+ this.clock = clock;
+ return this;
+ }
+
public Result build() {
return new Result(this);
}
@@ -304,7 +355,7 @@ public abstract class HealthCheck {
* Perform a check of the application component.
*
* @return if the component is healthy, a healthy {@link Result}; otherwise, an unhealthy {@link
- * Result} with a descriptive error message or exception
+ * Result} with a descriptive error message or exception
* @throws Exception if there is an unhandled error during the health check; this will result in
* a failed health check
*/
@@ -314,13 +365,21 @@ public abstract class HealthCheck {
* Executes the health check, catching and handling any exceptions raised by {@link #check()}.
*
* @return if the component is healthy, a healthy {@link Result}; otherwise, an unhealthy {@link
- * Result} with a descriptive error message or exception
+ * Result} with a descriptive error message or exception
*/
public Result execute() {
+ long start = clock().getTick();
+ Result result;
try {
- return check();
+ result = check();
} catch (Exception e) {
- return Result.unhealthy(e);
+ result = Result.unhealthy(e);
}
+ result.setDuration(TimeUnit.MILLISECONDS.convert(clock().getTick() - start, TimeUnit.NANOSECONDS));
+ return result;
+ }
+
+ protected Clock clock() {
+ return Clock.defaultClock();
}
}
diff --git a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckFilter.java b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckFilter.java
new file mode 100644
index 0000000..3802cc4
--- /dev/null
+++ b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckFilter.java
@@ -0,0 +1,21 @@
+package com.codahale.metrics.health;
+
+/**
+ * A filter used to determine whether or not a health check should be reported.
+ */
+@FunctionalInterface
+public interface HealthCheckFilter {
+ /**
+ * Matches all health checks, regardless of type or name.
+ */
+ HealthCheckFilter ALL = (name, healthCheck) -> true;
+
+ /**
+ * Returns {@code true} if the health check matches the filter; {@code false} otherwise.
+ *
+ * @param name the health check's name
+ * @param healthCheck the health check
+ * @return {@code true} if the health check matches the filter
+ */
+ boolean matches(String name, HealthCheck healthCheck);
+}
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 754832d..470bbd3 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,9 +1,9 @@
package com.codahale.metrics.health;
-import static com.codahale.metrics.health.HealthCheck.Result;
+import com.codahale.metrics.health.annotation.Async;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -13,22 +13,18 @@ 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.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.codahale.metrics.health.annotation.Async;
+import static com.codahale.metrics.health.HealthCheck.Result;
/**
* A registry for health checks.
@@ -64,8 +60,8 @@ public class 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.healthChecks = new ConcurrentHashMap<>();
+ this.listeners = new CopyOnWriteArrayList<>();
this.asyncExecutorService = asyncExecutorService;
}
@@ -99,19 +95,18 @@ public class HealthCheckRegistry {
* @param healthCheck the {@link HealthCheck} instance
*/
public void register(String name, HealthCheck healthCheck) {
- HealthCheck registered = null;
+ HealthCheck registered;
synchronized (lock) {
- if (!healthChecks.containsKey(name)) {
- registered = healthCheck;
- if (healthCheck.getClass().isAnnotationPresent(Async.class)) {
- registered = new AsyncHealthCheckDecorator(healthCheck, asyncExecutorService);
- }
- healthChecks.put(name, registered);
+ if (healthChecks.containsKey(name)) {
+ throw new IllegalArgumentException("A health check named " + name + " already exists");
}
+ registered = healthCheck;
+ if (healthCheck.getClass().isAnnotationPresent(Async.class)) {
+ registered = new AsyncHealthCheckDecorator(healthCheck, asyncExecutorService);
+ }
+ healthChecks.put(name, registered);
}
- if (registered != null) {
- onHealthCheckAdded(name, registered);
- }
+ onHealthCheckAdded(name, registered);
}
/**
@@ -120,7 +115,7 @@ public class HealthCheckRegistry {
* @param name the name of the {@link HealthCheck} instance
*/
public void unregister(String name) {
- HealthCheck healthCheck = null;
+ HealthCheck healthCheck;
synchronized (lock) {
healthCheck = healthChecks.remove(name);
if (healthCheck instanceof AsyncHealthCheckDecorator) {
@@ -138,7 +133,16 @@ public class HealthCheckRegistry {
* @return the names of all registered health checks
*/
public SortedSet<String> getNames() {
- return Collections.unmodifiableSortedSet(new TreeSet<String>(healthChecks.keySet()));
+ return Collections.unmodifiableSortedSet(new TreeSet<>(healthChecks.keySet()));
+ }
+
+ /**
+ * Returns the {@link HealthCheck} instance with a given name
+ *
+ * @param name the name of the {@link HealthCheck} instance
+ */
+ public HealthCheck getHealthCheck(String name) {
+ return healthChecks.get(name);
}
/**
@@ -162,10 +166,24 @@ public class HealthCheckRegistry {
* @return a map of the health check results
*/
public SortedMap<String, HealthCheck.Result> runHealthChecks() {
- final SortedMap<String, HealthCheck.Result> results = new TreeMap<String, HealthCheck.Result>();
+ return runHealthChecks(HealthCheckFilter.ALL);
+ }
+
+ /**
+ * Runs the registered health checks matching the filter and returns a map of the results.
+ *
+ * @param filter health check filter
+ * @return a map of the health check results
+ */
+ public SortedMap<String, HealthCheck.Result> runHealthChecks(HealthCheckFilter filter) {
+ final SortedMap<String, HealthCheck.Result> results = new TreeMap<>();
for (Map.Entry<String, HealthCheck> entry : healthChecks.entrySet()) {
- final Result result = entry.getValue().execute();
- results.put(entry.getKey(), result);
+ final String name = entry.getKey();
+ final HealthCheck healthCheck = entry.getValue();
+ if (filter.matches(name, healthCheck)) {
+ final Result result = entry.getValue().execute();
+ results.put(entry.getKey(), result);
+ }
}
return Collections.unmodifiableSortedMap(results);
}
@@ -177,17 +195,27 @@ public class HealthCheckRegistry {
* @return a map of the health check results
*/
public SortedMap<String, HealthCheck.Result> runHealthChecks(ExecutorService executor) {
- final Map<String, Future<HealthCheck.Result>> futures = new HashMap<String, Future<Result>>();
+ return runHealthChecks(executor, HealthCheckFilter.ALL);
+ }
+
+ /**
+ * Runs the registered health checks matching the filter in parallel and returns a map of the results.
+ *
+ * @param executor object to launch and track health checks progress
+ * @param filter health check filter
+ * @return a map of the health check results
+ */
+ public SortedMap<String, HealthCheck.Result> runHealthChecks(ExecutorService executor, HealthCheckFilter filter) {
+ final Map<String, Future<HealthCheck.Result>> futures = new HashMap<>();
for (final Map.Entry<String, HealthCheck> entry : healthChecks.entrySet()) {
- futures.put(entry.getKey(), executor.submit(new Callable<Result>() {
- @Override
- public Result call() throws Exception {
- return entry.getValue().execute();
- }
- }));
+ final String name = entry.getKey();
+ final HealthCheck healthCheck = entry.getValue();
+ if (filter.matches(name, healthCheck)) {
+ futures.put(name, executor.submit(() -> healthCheck.execute()));
+ }
}
- final SortedMap<String, HealthCheck.Result> results = new TreeMap<String, HealthCheck.Result>();
+ final SortedMap<String, HealthCheck.Result> results = new TreeMap<>();
for (Map.Entry<String, Future<Result>> entry : futures.entrySet()) {
try {
results.put(entry.getKey(), entry.getValue().get());
@@ -230,27 +258,12 @@ public class HealthCheckRegistry {
}
private static ScheduledExecutorService createExecutorService(int corePoolSize) {
- ScheduledExecutorService asyncExecutorService = Executors.newScheduledThreadPool(corePoolSize,
+ final ScheduledThreadPoolExecutor asyncExecutorService = new ScheduledThreadPoolExecutor(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);
- }
+ asyncExecutorService.setRemoveOnCancelPolicy(true);
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;
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 e65000e..5f1549c 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
@@ -10,9 +10,9 @@ import java.util.concurrent.atomic.AtomicReference;
*/
public class SharedHealthCheckRegistries {
private static final ConcurrentMap<String, HealthCheckRegistry> REGISTRIES =
- new ConcurrentHashMap<String, HealthCheckRegistry>();
+ new ConcurrentHashMap<>();
- private static AtomicReference<String> defaultRegistryName = new AtomicReference<String>();
+ private static AtomicReference<String> defaultRegistryName = new AtomicReference<>();
/* Visible for testing */
static void setDefaultRegistryName(AtomicReference<String> defaultRegistryName) {
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
index 91de06f..c8cfc4c 100644
--- 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
@@ -15,15 +15,15 @@ public @interface Async {
/**
* Enum representing the initial health states.
*/
- public enum InitialState {
- HEALTHY, UNHEALTHY;
+ enum InitialState {
+ HEALTHY, UNHEALTHY
}
/**
* Enum representing the possible schedule types.
*/
- public enum ScheduleType {
- FIXED_RATE, FIXED_DELAY;
+ enum ScheduleType {
+ FIXED_RATE, FIXED_DELAY
}
/**
@@ -48,7 +48,7 @@ public @interface Async {
long initialDelay() default 0;
/**
- * Time unit of initial delay and period.
+ * Time unit of initial delay, period and healthyTtl.
*
* @return time unit
*/
@@ -61,4 +61,15 @@ public @interface Async {
*/
InitialState initialState() default InitialState.HEALTHY;
+ /**
+ * How long a healthy result is considered valid before being ignored.
+ *
+ * Handles cases where the asynchronous healthcheck did not run (for example thread starvation).
+ *
+ * Defaults to 2 * period
+ *
+ * @return healthy result time to live
+ */
+ long healthyTtl() default -1;
+
}
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
index f3f2f61..5e0d87d 100644
--- a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/AsyncHealthCheckDecoratorTest.java
+++ b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/AsyncHealthCheckDecoratorTest.java
@@ -1,29 +1,43 @@
package com.codahale.metrics.health;
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.health.annotation.Async;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
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.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.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 static final long CURRENT_TIME = 1551002401000L;
+
+ private static final Clock FIXED_CLOCK = clockWithFixedTime(CURRENT_TIME);
+
+ private static final HealthCheck.Result EXPECTED_EXPIRED_RESULT = HealthCheck.Result
+ .builder()
+ .usingClock(FIXED_CLOCK)
+ .unhealthy()
+ .withMessage("Result was healthy but it expired 1 milliseconds ago")
+ .build();
+
private final HealthCheck mockHealthCheck = mock(HealthCheck.class);
private final ScheduledExecutorService mockExecutorService = mock(ScheduledExecutorService.class);
+
+ @SuppressWarnings("rawtypes")
private final ScheduledFuture mockFuture = mock(ScheduledFuture.class);
@Test(expected = IllegalArgumentException.class)
@@ -86,6 +100,7 @@ public class AsyncHealthCheckDecoratorTest {
}
@Test
+ @SuppressWarnings("unchecked")
public void tearDownTriggersCancellation() throws Exception {
when(mockExecutorService.scheduleAtFixedRate(any(Runnable.class), eq(0L), eq(1L), eq(TimeUnit.SECONDS))).
thenReturn(mockFuture);
@@ -100,6 +115,7 @@ public class AsyncHealthCheckDecoratorTest {
}
@Test
+ @SuppressWarnings("unchecked")
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)))
@@ -121,6 +137,7 @@ public class AsyncHealthCheckDecoratorTest {
}
@Test
+ @SuppressWarnings("unchecked")
public void exceptionInDecoratedHealthCheckWontAffectAsyncDecorator() throws Exception {
Exception exception = new Exception("TestException");
when(mockExecutorService.scheduleAtFixedRate(any(Runnable.class), eq(0L), eq(1L), eq(TimeUnit.SECONDS)))
@@ -140,11 +157,59 @@ public class AsyncHealthCheckDecoratorTest {
assertThat(result.getError()).isEqualTo(exception);
}
+ @Test
+ public void returnUnhealthyIfPreviousResultIsExpiredBasedOnTtl() throws Exception {
+ HealthCheck healthCheck = new HealthyAsyncHealthCheckWithExpiredExplicitTtlInMilliseconds();
+ AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(healthCheck, mockExecutorService, FIXED_CLOCK);
+
+ ArgumentCaptor<Runnable> runnableCaptor = forClass(Runnable.class);
+ verify(mockExecutorService, times(1)).scheduleAtFixedRate(runnableCaptor.capture(),
+ eq(0L), eq(1000L), eq(TimeUnit.MILLISECONDS));
+ Runnable capturedRunnable = runnableCaptor.getValue();
+ capturedRunnable.run();
+
+ HealthCheck.Result result = asyncDecorator.check();
+
+ assertThat(result).isEqualTo(EXPECTED_EXPIRED_RESULT);
+ }
+
+ @Test
+ public void returnUnhealthyIfPreviousResultIsExpiredBasedOnPeriod() throws Exception {
+ HealthCheck healthCheck = new HealthyAsyncHealthCheckWithExpiredTtlInMillisecondsBasedOnPeriod();
+ AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(healthCheck, mockExecutorService, FIXED_CLOCK);
+
+ ArgumentCaptor<Runnable> runnableCaptor = forClass(Runnable.class);
+ verify(mockExecutorService, times(1)).scheduleAtFixedRate(runnableCaptor.capture(),
+ eq(0L), eq(1000L), eq(TimeUnit.MILLISECONDS));
+ Runnable capturedRunnable = runnableCaptor.getValue();
+ capturedRunnable.run();
+
+ HealthCheck.Result result = asyncDecorator.check();
+
+ assertThat(result).isEqualTo(EXPECTED_EXPIRED_RESULT);
+ }
+
+ @Test
+ public void convertTtlToMillisecondsWhenCheckingExpiration() throws Exception {
+ HealthCheck healthCheck = new HealthyAsyncHealthCheckWithExpiredExplicitTtlInSeconds();
+ AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(healthCheck, mockExecutorService, FIXED_CLOCK);
+
+ 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).isEqualTo(EXPECTED_EXPIRED_RESULT);
+ }
+
@Async(period = -1)
private static class NegativePeriodAsyncHealthCheck extends HealthCheck {
@Override
- protected Result check() throws Exception {
+ protected Result check() {
return null;
}
}
@@ -153,7 +218,7 @@ public class AsyncHealthCheckDecoratorTest {
private static class ZeroPeriodAsyncHealthCheck extends HealthCheck {
@Override
- protected Result check() throws Exception {
+ protected Result check() {
return null;
}
}
@@ -162,7 +227,7 @@ public class AsyncHealthCheckDecoratorTest {
private static class NegativeInitialDelayAsyncHealthCheck extends HealthCheck {
@Override
- protected Result check() throws Exception {
+ protected Result check() {
return null;
}
}
@@ -171,7 +236,7 @@ public class AsyncHealthCheckDecoratorTest {
private static class DefaultAsyncHealthCheck extends HealthCheck {
@Override
- protected Result check() throws Exception {
+ protected Result check() {
return null;
}
}
@@ -180,7 +245,7 @@ public class AsyncHealthCheckDecoratorTest {
private static class FixedDelayAsyncHealthCheck extends HealthCheck {
@Override
- protected Result check() throws Exception {
+ protected Result check() {
return null;
}
}
@@ -189,7 +254,7 @@ public class AsyncHealthCheckDecoratorTest {
private static class UnhealthyAsyncHealthCheck extends HealthCheck {
@Override
- protected Result check() throws Exception {
+ protected Result check() {
return null;
}
}
@@ -221,4 +286,45 @@ public class AsyncHealthCheckDecoratorTest {
}
}
+ @Async(period = 1000, initialState = Async.InitialState.UNHEALTHY, healthyTtl = 3000, unit = TimeUnit.MILLISECONDS)
+ private static class HealthyAsyncHealthCheckWithExpiredExplicitTtlInMilliseconds extends HealthCheck {
+
+ @Override
+ protected Result check() {
+ return Result.builder().usingClock(clockWithFixedTime(CURRENT_TIME - 3001L)).healthy().build();
+ }
+ }
+
+ @Async(period = 1, initialState = Async.InitialState.UNHEALTHY, healthyTtl = 5, unit = TimeUnit.SECONDS)
+ private static class HealthyAsyncHealthCheckWithExpiredExplicitTtlInSeconds extends HealthCheck {
+
+ @Override
+ protected Result check() {
+ return Result.builder().usingClock(clockWithFixedTime(CURRENT_TIME - 5001L)).healthy().build();
+ }
+ }
+
+ @Async(period = 1000, initialState = Async.InitialState.UNHEALTHY, unit = TimeUnit.MILLISECONDS)
+ private static class HealthyAsyncHealthCheckWithExpiredTtlInMillisecondsBasedOnPeriod extends HealthCheck {
+
+ @Override
+ protected Result check() {
+ return Result.builder().usingClock(clockWithFixedTime(CURRENT_TIME - 2001L)).healthy().build();
+ }
+ }
+
+ private static Clock clockWithFixedTime(final long time) {
+ return new Clock() {
+ @Override
+ public long getTick() {
+ return 0;
+ }
+
+ @Override
+ public long getTime() {
+ return time;
+ }
+ };
+ }
+
}
diff --git a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckFilterTest.java b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckFilterTest.java
new file mode 100644
index 0000000..1680111
--- /dev/null
+++ b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckFilterTest.java
@@ -0,0 +1,14 @@
+package com.codahale.metrics.health;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+public class HealthCheckFilterTest {
+
+ @Test
+ public void theAllFilterMatchesAllHealthChecks() {
+ assertThat(HealthCheckFilter.ALL.matches("", mock(HealthCheck.class))).isTrue();
+ }
+}
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 3d3171b..bffaf89 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
@@ -4,8 +4,8 @@ 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.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -37,16 +37,19 @@ public class HealthCheckRegistryTest {
private final HealthCheck.Result ar = mock(HealthCheck.Result.class);
private final HealthCheck ahc = new TestAsyncHealthCheck(ar);
+
+ @SuppressWarnings("rawtypes")
private final ScheduledFuture af = mock(ScheduledFuture.class);
@Before
- public void setUp() throws Exception {
+ @SuppressWarnings("unchecked")
+ public void setUp() {
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);
+ when(executorService.scheduleAtFixedRate(any(AsyncHealthCheckDecorator.class), eq(0L), eq(10L), eq(TimeUnit.SECONDS)))
+ .thenReturn(af);
registry.register("hc1", hc1);
registry.register("hc2", hc2);
@@ -67,6 +70,11 @@ public class HealthCheckRegistryTest {
verify(af).cancel(true);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void registeringHealthCheckTwiceThrowsException() {
+ registry.register("hc1", hc1);
+ }
+
@Test
public void registeringHealthCheckTriggersNotification() {
verify(listener).onHealthCheckAdded("hc1", hc1);
@@ -112,7 +120,7 @@ public class HealthCheckRegistryTest {
}
@Test
- public void runsRegisteredHealthChecks() throws Exception {
+ public void runsRegisteredHealthChecks() {
final Map<String, HealthCheck.Result> results = registry.runHealthChecks();
assertThat(results).contains(entry("hc1", r1));
@@ -120,6 +128,20 @@ public class HealthCheckRegistryTest {
assertThat(results).containsKey("ahc");
}
+ @Test
+ public void runsRegisteredHealthChecksWithFilter() {
+ final Map<String, HealthCheck.Result> results = registry.runHealthChecks((name, healthCheck) -> "hc1".equals(name));
+
+ assertThat(results).containsOnly(entry("hc1", r1));
+ }
+
+ @Test
+ public void runsRegisteredHealthChecksWithNonMatchingFilter() {
+ final Map<String, HealthCheck.Result> results = registry.runHealthChecks((name, healthCheck) -> false);
+
+ assertThat(results).isEmpty();
+ }
+
@Test
public void runsRegisteredHealthChecksInParallel() throws Exception {
final ExecutorService executor = Executors.newFixedThreadPool(10);
@@ -134,7 +156,30 @@ public class HealthCheckRegistryTest {
}
@Test
- public void removesRegisteredHealthChecks() throws Exception {
+ public void runsRegisteredHealthChecksInParallelWithNonMatchingFilter() throws Exception {
+ final ExecutorService executor = Executors.newFixedThreadPool(10);
+ final Map<String, HealthCheck.Result> results = registry.runHealthChecks(executor, (name, healthCheck) -> false);
+
+ executor.shutdown();
+ executor.awaitTermination(1, TimeUnit.SECONDS);
+
+ assertThat(results).isEmpty();
+ }
+
+ @Test
+ public void runsRegisteredHealthChecksInParallelWithFilter() throws Exception {
+ final ExecutorService executor = Executors.newFixedThreadPool(10);
+ final Map<String, HealthCheck.Result> results = registry.runHealthChecks(executor,
+ (name, healthCheck) -> "hc2".equals(name));
+
+ executor.shutdown();
+ executor.awaitTermination(1, TimeUnit.SECONDS);
+
+ assertThat(results).containsOnly(entry("hc2", r2));
+ }
+
+ @Test
+ public void removesRegisteredHealthChecks() {
registry.unregister("hc1");
final Map<String, HealthCheck.Result> results = registry.runHealthChecks();
@@ -145,23 +190,23 @@ public class HealthCheckRegistryTest {
}
@Test
- public void hasASetOfHealthCheckNames() throws Exception {
+ public void hasASetOfHealthCheckNames() {
assertThat(registry.getNames()).containsOnly("hc1", "hc2", "ahc");
}
@Test
- public void runsHealthChecksByName() throws Exception {
+ public void runsHealthChecksByName() {
assertThat(registry.runHealthCheck("hc1")).isEqualTo(r1);
}
@Test
- public void doesNotRunNonexistentHealthChecks() throws Exception {
+ public void doesNotRunNonexistentHealthChecks() {
try {
registry.runHealthCheck("what");
failBecauseExceptionWasNotThrown(NoSuchElementException.class);
} catch (NoSuchElementException e) {
assertThat(e.getMessage())
- .isEqualTo("No health check named what exists");
+ .isEqualTo("No health check named what exists");
}
}
@@ -175,7 +220,7 @@ public class HealthCheckRegistryTest {
}
@Override
- protected Result check() throws Exception {
+ protected Result check() {
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 8d3a88a..13c5b3a 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,12 +1,22 @@
package com.codahale.metrics.health;
+import com.codahale.metrics.Clock;
+import org.junit.Test;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import org.junit.Test;
-
public class HealthCheckTest {
+
+ private static final DateTimeFormatter DATE_TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+
private static class ExampleHealthCheck extends HealthCheck {
private final HealthCheck underlying;
@@ -15,7 +25,7 @@ public class HealthCheckTest {
}
@Override
- protected Result check() throws Exception {
+ protected Result check() {
return underlying.execute();
}
}
@@ -24,7 +34,7 @@ public class HealthCheckTest {
private final HealthCheck healthCheck = new ExampleHealthCheck(underlying);
@Test
- public void canHaveHealthyResults() throws Exception {
+ public void canHaveHealthyResults() {
final HealthCheck.Result result = HealthCheck.Result.healthy();
assertThat(result.isHealthy())
@@ -38,7 +48,7 @@ public class HealthCheckTest {
}
@Test
- public void canHaveHealthyResultsWithMessages() throws Exception {
+ public void canHaveHealthyResultsWithMessages() {
final HealthCheck.Result result = HealthCheck.Result.healthy("woo");
assertThat(result.isHealthy())
@@ -52,7 +62,7 @@ public class HealthCheckTest {
}
@Test
- public void canHaveHealthyResultsWithFormattedMessages() throws Exception {
+ public void canHaveHealthyResultsWithFormattedMessages() {
final HealthCheck.Result result = HealthCheck.Result.healthy("foo %s", "bar");
assertThat(result.isHealthy())
@@ -66,7 +76,7 @@ public class HealthCheckTest {
}
@Test
- public void canHaveUnhealthyResults() throws Exception {
+ public void canHaveUnhealthyResults() {
final HealthCheck.Result result = HealthCheck.Result.unhealthy("bad");
assertThat(result.isHealthy())
@@ -80,7 +90,7 @@ public class HealthCheckTest {
}
@Test
- public void canHaveUnhealthyResultsWithFormattedMessages() throws Exception {
+ public void canHaveUnhealthyResultsWithFormattedMessages() {
final HealthCheck.Result result = HealthCheck.Result.unhealthy("foo %s %d", "bar", 123);
assertThat(result.isHealthy())
@@ -94,7 +104,7 @@ public class HealthCheckTest {
}
@Test
- public void canHaveUnhealthyResultsWithExceptions() throws Exception {
+ public void canHaveUnhealthyResultsWithExceptions() {
final RuntimeException e = mock(RuntimeException.class);
when(e.getMessage()).thenReturn("oh noes");
@@ -111,80 +121,96 @@ public class HealthCheckTest {
}
@Test
- public void canHaveHealthyBuilderWithDetail() throws Exception {
+ public void canHaveHealthyBuilderWithFormattedMessage() {
+ final HealthCheck.Result result = HealthCheck.Result.builder()
+ .healthy()
+ .withMessage("There are %d %s in the %s", 42, "foos", "bar")
+ .build();
+
+ assertThat(result.isHealthy())
+ .isTrue();
+
+ assertThat(result.getMessage())
+ .isEqualTo("There are 42 foos in the bar");
+ }
+
+ @Test
+ public void canHaveHealthyBuilderWithDetail() {
final HealthCheck.Result result = HealthCheck.Result.builder()
- .healthy()
- .withDetail("detail", "value")
- .build();
+ .healthy()
+ .withDetail("detail", "value")
+ .build();
assertThat(result.isHealthy())
- .isTrue();
+ .isTrue();
assertThat(result.getMessage())
- .isNull();
+ .isNull();
assertThat(result.getError())
- .isNull();
+ .isNull();
assertThat(result.getDetails())
- .containsEntry("detail", "value");
+ .containsEntry("detail", "value");
}
@Test
- public void canHaveUnHealthyBuilderWithDetail() throws Exception {
+ public void canHaveUnHealthyBuilderWithDetail() {
final HealthCheck.Result result = HealthCheck.Result.builder()
- .unhealthy()
- .withDetail("detail", "value")
- .build();
+ .unhealthy()
+ .withDetail("detail", "value")
+ .build();
assertThat(result.isHealthy())
- .isFalse();
+ .isFalse();
assertThat(result.getMessage())
- .isNull();
+ .isNull();
assertThat(result.getError())
- .isNull();
+ .isNull();
assertThat(result.getDetails())
- .containsEntry("detail", "value");
+ .containsEntry("detail", "value");
}
@Test
- public void canHaveUnHealthyBuilderWithDetailAndError() throws Exception {
+ public void canHaveUnHealthyBuilderWithDetailAndError() {
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();
+ .builder()
+ .unhealthy(e)
+ .withDetail("detail", "value")
+ .build();
assertThat(result.isHealthy())
- .isFalse();
+ .isFalse();
assertThat(result.getMessage())
- .isEqualTo("oh noes");
+ .isEqualTo("oh noes");
assertThat(result.getError())
- .isEqualTo(e);
+ .isEqualTo(e);
assertThat(result.getDetails())
- .containsEntry("detail", "value");
+ .containsEntry("detail", "value");
}
@Test
- public void returnsResultsWhenExecuted() throws Exception {
+ public void returnsResultsWhenExecuted() {
final HealthCheck.Result result = mock(HealthCheck.Result.class);
when(underlying.execute()).thenReturn(result);
assertThat(healthCheck.execute())
.isEqualTo(result);
+
+ verify(result).setDuration(anyLong());
}
@Test
- public void wrapsExceptionsWhenExecuted() throws Exception {
+ public void wrapsExceptionsWhenExecuted() {
final RuntimeException e = mock(RuntimeException.class);
when(e.getMessage()).thenReturn("oh noes");
@@ -199,17 +225,55 @@ public class HealthCheckTest {
.isEqualTo(e);
assertThat(actual.getDetails())
.isNull();
+ assertThat(actual.getDuration())
+ .isGreaterThanOrEqualTo(0);
+ }
+
+ @Test
+ public void canHaveUserSuppliedClockForTimestamp() {
+ ZonedDateTime dateTime = ZonedDateTime.now().minusMinutes(10);
+ Clock clock = clockWithFixedTime(dateTime);
+
+ HealthCheck.Result result = HealthCheck.Result.builder()
+ .healthy()
+ .usingClock(clock)
+ .build();
+
+ assertThat(result.isHealthy()).isTrue();
+
+ assertThat(result.getTime()).isEqualTo(clock.getTime());
+
+ assertThat(result.getTimestamp())
+ .isEqualTo(DATE_TIME_FORMATTER.format(dateTime));
}
@Test
- public void toStringWorksEvenForNullAttributes() throws Exception {
+ public void toStringWorksEvenForNullAttributes() {
+ ZonedDateTime dateTime = ZonedDateTime.now().minusMinutes(25);
+ Clock clock = clockWithFixedTime(dateTime);
+
final HealthCheck.Result resultWithNullDetailValue = HealthCheck.Result.builder()
- .unhealthy()
- .withDetail("aNullDetail", null)
- .build();
+ .unhealthy()
+ .withDetail("aNullDetail", null)
+ .usingClock(clock)
+ .build();
assertThat(resultWithNullDetailValue.toString())
- .contains(
- "Result{isHealthy=false, timestamp=", // Skip the timestamp part of the String.
- ", aNullDetail=null}");
+ .contains(
+ "Result{isHealthy=false, duration=0, timestamp=" + DATE_TIME_FORMATTER.format(dateTime),
+ ", aNullDetail=null}");
+ }
+
+ private static Clock clockWithFixedTime(ZonedDateTime dateTime) {
+ return new Clock() {
+ @Override
+ public long getTick() {
+ return 0;
+ }
+
+ @Override
+ public long getTime() {
+ return dateTime.toInstant().toEpochMilli();
+ }
+ };
}
}
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
index c9c0254..b7a1b12 100644
--- a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/SharedHealthCheckRegistriesTest.java
+++ b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/SharedHealthCheckRegistriesTest.java
@@ -16,7 +16,7 @@ public class SharedHealthCheckRegistriesTest {
@Before
public void setUp() {
- SharedHealthCheckRegistries.setDefaultRegistryName(new AtomicReference<String>());
+ SharedHealthCheckRegistries.setDefaultRegistryName(new AtomicReference<>());
SharedHealthCheckRegistries.clear();
}
diff --git a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/jvm/ThreadDeadlockHealthCheckTest.java b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/jvm/ThreadDeadlockHealthCheckTest.java
index 43599c0..e878ff7 100644
--- a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/jvm/ThreadDeadlockHealthCheckTest.java
+++ b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/jvm/ThreadDeadlockHealthCheckTest.java
@@ -14,19 +14,19 @@ import static org.mockito.Mockito.when;
public class ThreadDeadlockHealthCheckTest {
@Test
- public void isHealthyIfNoThreadsAreDeadlocked() throws Exception {
+ public void isHealthyIfNoThreadsAreDeadlocked() {
final ThreadDeadlockDetector detector = mock(ThreadDeadlockDetector.class);
final ThreadDeadlockHealthCheck healthCheck = new ThreadDeadlockHealthCheck(detector);
- when(detector.getDeadlockedThreads()).thenReturn(Collections.<String>emptySet());
+ when(detector.getDeadlockedThreads()).thenReturn(Collections.emptySet());
assertThat(healthCheck.execute().isHealthy())
.isTrue();
}
@Test
- public void isUnhealthyIfThreadsAreDeadlocked() throws Exception {
- final Set<String> threads = new TreeSet<String>();
+ public void isUnhealthyIfThreadsAreDeadlocked() {
+ final Set<String> threads = new TreeSet<>();
threads.add("one");
threads.add("two");
@@ -45,7 +45,7 @@ public class ThreadDeadlockHealthCheckTest {
}
@Test
- public void automaticallyUsesThePlatformThreadBeans() throws Exception {
+ public void automaticallyUsesThePlatformThreadBeans() {
final ThreadDeadlockHealthCheck healthCheck = new ThreadDeadlockHealthCheck();
assertThat(healthCheck.execute().isHealthy())
.isTrue();
diff --git a/metrics-httpasyncclient/pom.xml b/metrics-httpasyncclient/pom.xml
index d5c0870..4cbbb58 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-httpasyncclient</artifactId>
@@ -16,26 +16,92 @@
durations and rates, and other useful information.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.httpasyncclient</javaModuleName>
+ <http-async-client.version>4.1.5</http-async-client.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.5.14</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ <version>4.4.16</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore-nio</artifactId>
+ <version>4.4.16</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-httpclient</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
- <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>
+ <version>${http-async-client.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore-nio</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
</dependency>
</dependencies>
</project>
diff --git a/metrics-httpasyncclient/src/main/java/com/codahale/metrics/httpasyncclient/InstrumentedNClientConnManager.java b/metrics-httpasyncclient/src/main/java/com/codahale/metrics/httpasyncclient/InstrumentedNClientConnManager.java
index dbcc0cb..e541f5d 100644
--- a/metrics-httpasyncclient/src/main/java/com/codahale/metrics/httpasyncclient/InstrumentedNClientConnManager.java
+++ b/metrics-httpasyncclient/src/main/java/com/codahale/metrics/httpasyncclient/InstrumentedNClientConnManager.java
@@ -1,9 +1,6 @@
package com.codahale.metrics.httpasyncclient;
-import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
-import static com.codahale.metrics.MetricRegistry.name;
-import java.util.concurrent.TimeUnit;
import org.apache.http.config.Registry;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.SchemePortResolver;
@@ -14,42 +11,26 @@ import org.apache.http.nio.conn.NHttpConnectionFactory;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.reactor.ConnectingIOReactor;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
public class InstrumentedNClientConnManager extends PoolingNHttpClientConnectionManager {
public InstrumentedNClientConnManager(final ConnectingIOReactor ioreactor, final NHttpConnectionFactory<ManagedNHttpClientConnection> connFactory, final SchemePortResolver schemePortResolver, final MetricRegistry metricRegistry, final Registry<SchemeIOSessionStrategy> iosessionFactoryRegistry, final long timeToLive, final TimeUnit tunit, final DnsResolver dnsResolver, final String name) {
super(ioreactor, connFactory, iosessionFactoryRegistry, schemePortResolver, dnsResolver, timeToLive, tunit);
- metricRegistry.register(name(NHttpClientConnectionManager.class, name, "available-connections"),
- new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // this acquires a lock on the connection pool; remove if contention sucks
- return getTotalStats().getAvailable();
- }
- });
- metricRegistry.register(name(NHttpClientConnectionManager.class, name, "leased-connections"),
- new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // this acquires a lock on the connection pool; remove if contention sucks
- return getTotalStats().getLeased();
- }
- });
- metricRegistry.register(name(NHttpClientConnectionManager.class, name, "max-connections"),
- new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // this acquires a lock on the connection pool; remove if contention sucks
- return getTotalStats().getMax();
- }
- });
- metricRegistry.register(name(NHttpClientConnectionManager.class, name, "pending-connections"),
- new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // this acquires a lock on the connection pool; remove if contention sucks
- return getTotalStats().getPending();
- }
- });
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(NHttpClientConnectionManager.class, name, "available-connections"),
+ () -> getTotalStats().getAvailable());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(NHttpClientConnectionManager.class, name, "leased-connections"),
+ () -> getTotalStats().getLeased());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(NHttpClientConnectionManager.class, name, "max-connections"),
+ () -> getTotalStats().getMax());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(NHttpClientConnectionManager.class, name, "pending-connections"),
+ () -> getTotalStats().getPending());
}
}
diff --git a/metrics-httpasyncclient/src/main/java/com/codahale/metrics/httpasyncclient/InstrumentedNHttpClientBuilder.java b/metrics-httpasyncclient/src/main/java/com/codahale/metrics/httpasyncclient/InstrumentedNHttpClientBuilder.java
index 27365d4..721ca09 100644
--- a/metrics-httpasyncclient/src/main/java/com/codahale/metrics/httpasyncclient/InstrumentedNHttpClientBuilder.java
+++ b/metrics-httpasyncclient/src/main/java/com/codahale/metrics/httpasyncclient/InstrumentedNHttpClientBuilder.java
@@ -4,8 +4,10 @@ import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.codahale.metrics.httpclient.HttpClientMetricNameStrategies;
import com.codahale.metrics.httpclient.HttpClientMetricNameStrategy;
+
import java.io.IOException;
import java.util.concurrent.Future;
+
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.concurrent.FutureCallback;
@@ -15,6 +17,8 @@ import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
import org.apache.http.nio.protocol.HttpAsyncResponseConsumer;
import org.apache.http.protocol.HttpContext;
+import static java.util.Objects.requireNonNull;
+
public class InstrumentedNHttpClientBuilder extends HttpAsyncClientBuilder {
private final MetricRegistry metricRegistry;
private final String name;
@@ -63,16 +67,11 @@ public class InstrumentedNHttpClientBuilder extends HttpAsyncClientBuilder {
final Timer.Context timerContext;
try {
timerContext = timer(requestProducer.generateRequest()).time();
- } catch (IOException ex) {
- throw new AssertionError(ex);
- } catch (HttpException ex) {
- throw new AssertionError(ex);
- }
- try {
- return ac.execute(requestProducer, responseConsumer, context, callback);
- } finally {
- timerContext.stop();
+ } catch (IOException | HttpException ex) {
+ throw new RuntimeException(ex);
}
+ return ac.execute(requestProducer, responseConsumer, context,
+ new TimingFutureCallback<>(callback, timerContext));
}
@Override
@@ -82,4 +81,39 @@ public class InstrumentedNHttpClientBuilder extends HttpAsyncClientBuilder {
};
}
+ private static class TimingFutureCallback<T> implements FutureCallback<T> {
+ private final FutureCallback<T> callback;
+ private final Timer.Context timerContext;
+
+ private TimingFutureCallback(FutureCallback<T> callback,
+ Timer.Context timerContext) {
+ this.callback = callback;
+ this.timerContext = requireNonNull(timerContext, "timerContext");
+ }
+
+ @Override
+ public void completed(T result) {
+ timerContext.stop();
+ if (callback != null) {
+ callback.completed(result);
+ }
+ }
+
+ @Override
+ public void failed(Exception ex) {
+ timerContext.stop();
+ if (callback != null) {
+ callback.failed(ex);
+ }
+ }
+
+ @Override
+ public void cancelled() {
+ timerContext.stop();
+ if (callback != null) {
+ callback.cancelled();
+ }
+ }
+ }
+
}
diff --git a/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/HttpClientTestBase.java b/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/HttpClientTestBase.java
new file mode 100644
index 0000000..12ff992
--- /dev/null
+++ b/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/HttpClientTestBase.java
@@ -0,0 +1,71 @@
+package com.codahale.metrics.httpasyncclient;
+
+import org.apache.http.HttpHost;
+import org.apache.http.impl.nio.bootstrap.HttpServer;
+import org.apache.http.impl.nio.bootstrap.ServerBootstrap;
+import org.apache.http.nio.protocol.BasicAsyncRequestHandler;
+import org.apache.http.nio.reactor.ListenerEndpoint;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.junit.After;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.util.concurrent.TimeUnit;
+
+public abstract class HttpClientTestBase {
+
+ /**
+ * {@link HttpRequestHandler} that responds with a {@code 200 OK}.
+ */
+ public static final HttpRequestHandler STATUS_OK = (request, response, context) -> response.setStatusCode(200);
+
+ private HttpServer server;
+
+ /**
+ * @return A free local port or {@code -1} on error.
+ */
+ public static int findAvailableLocalPort() {
+ try (ServerSocket socket = new ServerSocket(0)) {
+ return socket.getLocalPort();
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Start a local server that uses the {@code handler} to handle requests.
+ * <p>
+ * The server will be (if started) terminated in the {@link #tearDown()} {@link After} method.
+ *
+ * @param handler The request handler that will be used to respond to every request.
+ * @return The {@link HttpHost} of the server
+ * @throws IOException in case it's not possible to start the server
+ * @throws InterruptedException in case the server's main thread was interrupted
+ */
+ public HttpHost startServerWithGlobalRequestHandler(HttpRequestHandler handler)
+ throws IOException, InterruptedException {
+ // If there is an existing instance, terminate it
+ tearDown();
+
+ ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap();
+
+ serverBootstrap.registerHandler("/*", new BasicAsyncRequestHandler(handler));
+
+ server = serverBootstrap.create();
+ server.start();
+
+ ListenerEndpoint endpoint = server.getEndpoint();
+ endpoint.waitFor();
+
+ InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
+ return new HttpHost("localhost", address.getPort(), "http");
+ }
+
+ @After
+ public void tearDown() {
+ if (server != null) {
+ server.shutdown(5, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/InstrumentedHttpClientsTest.java b/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/InstrumentedHttpClientsTest.java
index ca53b51..f0decf3 100644
--- a/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/InstrumentedHttpClientsTest.java
+++ b/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/InstrumentedHttpClientsTest.java
@@ -4,47 +4,54 @@ import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricRegistryListener;
import com.codahale.metrics.Timer;
import com.codahale.metrics.httpclient.HttpClientMetricNameStrategy;
-import com.codahale.metrics.httpclient.InstrumentedHttpClients;
import org.apache.http.HttpRequest;
-import org.apache.http.client.HttpClient;
+import org.apache.http.HttpHost;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.nio.client.HttpAsyncClient;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class InstrumentedHttpClientsTest extends HttpClientTestBase {
-public class InstrumentedHttpClientsTest {
- private final HttpClientMetricNameStrategy metricNameStrategy =
- mock(HttpClientMetricNameStrategy.class);
- private final MetricRegistryListener registryListener =
- mock(MetricRegistryListener.class);
private final MetricRegistry metricRegistry = new MetricRegistry();
-
- private HttpAsyncClient hac;
- @Before
- public void setUp() throws Exception {
- CloseableHttpAsyncClient chac = new InstrumentedNHttpClientBuilder(metricRegistry, metricNameStrategy).build();
- chac.start();
- hac = chac;
- metricRegistry.addListener(registryListener);
- }
+
+ private HttpAsyncClient asyncHttpClient;
+ @Mock
+ private HttpClientMetricNameStrategy metricNameStrategy;
+ @Mock
+ private MetricRegistryListener registryListener;
@Test
public void registersExpectedMetricsGivenNameStrategy() throws Exception {
- final HttpGet get = new HttpGet("http://example.com?q=anything");
- final String metricName = "some.made.up.metric.name";
+ HttpHost host = startServerWithGlobalRequestHandler(STATUS_OK);
+ final HttpGet get = new HttpGet("/q=anything");
+ final String metricName = MetricRegistry.name("some.made.up.metric.name");
- when(metricNameStrategy.getNameFor(anyString(), any(HttpRequest.class)))
+ when(metricNameStrategy.getNameFor(any(), any(HttpRequest.class)))
.thenReturn(metricName);
- hac.execute(get,null).get();
+ asyncHttpClient.execute(host, get, null).get();
verify(registryListener).onTimerAdded(eq(metricName), any(Timer.class));
}
+
+ @Before
+ public void setUp() throws Exception {
+ CloseableHttpAsyncClient chac = new InstrumentedNHttpClientBuilder(metricRegistry, metricNameStrategy).build();
+ chac.start();
+ asyncHttpClient = chac;
+ metricRegistry.addListener(registryListener);
+ }
}
diff --git a/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/InstrumentedHttpClientsTimerTest.java b/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/InstrumentedHttpClientsTimerTest.java
new file mode 100644
index 0000000..5a3063b
--- /dev/null
+++ b/metrics-httpasyncclient/src/test/java/com/codahale/metrics/httpasyncclient/InstrumentedHttpClientsTimerTest.java
@@ -0,0 +1,134 @@
+package com.codahale.metrics.httpasyncclient;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.httpclient.HttpClientMetricNameStrategy;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.concurrent.FutureCallback;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.nio.client.HttpAsyncClient;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+@Ignore("The tests are flaky")
+public class InstrumentedHttpClientsTimerTest extends HttpClientTestBase {
+
+ private HttpAsyncClient asyncHttpClient;
+
+ @Mock
+ private Timer.Context context;
+
+ @Mock
+ private MetricRegistry metricRegistry;
+
+
+ @Before
+ public void setUp() throws Exception {
+ CloseableHttpAsyncClient chac = new InstrumentedNHttpClientBuilder(metricRegistry,
+ mock(HttpClientMetricNameStrategy.class)).build();
+ chac.start();
+ asyncHttpClient = chac;
+
+ Timer timer = mock(Timer.class);
+ when(timer.time()).thenReturn(context);
+ when(metricRegistry.timer(any())).thenReturn(timer);
+ }
+
+ @Test
+ public void timerIsStoppedCorrectly() throws Exception {
+ HttpHost host = startServerWithGlobalRequestHandler(STATUS_OK);
+ HttpGet get = new HttpGet("/?q=anything");
+
+ // Timer hasn't been stopped prior to executing the request
+ verify(context, never()).stop();
+
+ Future<HttpResponse> responseFuture = asyncHttpClient.execute(host, get, null);
+
+ // Timer should still be running
+ verify(context, never()).stop();
+
+ responseFuture.get(20, TimeUnit.SECONDS);
+
+ // After the computation is complete timer must be stopped
+ // Materialzing the future and calling the future callback is not an atomic operation so
+ // we need to wait for callback to succeed
+ verify(context, timeout(200).times(1)).stop();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void timerIsStoppedCorrectlyWithProvidedFutureCallbackCompleted() throws Exception {
+ HttpHost host = startServerWithGlobalRequestHandler(STATUS_OK);
+ HttpGet get = new HttpGet("/?q=something");
+
+ FutureCallback<HttpResponse> futureCallback = mock(FutureCallback.class);
+
+ // Timer hasn't been stopped prior to executing the request
+ verify(context, never()).stop();
+
+ Future<HttpResponse> responseFuture = asyncHttpClient.execute(host, get, futureCallback);
+
+ // Timer should still be running
+ verify(context, never()).stop();
+
+ responseFuture.get(20, TimeUnit.SECONDS);
+
+ // Callback must have been called
+ assertThat(responseFuture.isDone()).isTrue();
+ // After the computation is complete timer must be stopped
+ // Materialzing the future and calling the future callback is not an atomic operation so
+ // we need to wait for callback to succeed
+ verify(futureCallback, timeout(200).times(1)).completed(any(HttpResponse.class));
+ verify(context, timeout(200).times(1)).stop();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void timerIsStoppedCorrectlyWithProvidedFutureCallbackFailed() throws Exception {
+ // There should be nothing listening on this port
+ HttpHost host = HttpHost.create(String.format("http://127.0.0.1:%d", findAvailableLocalPort()));
+ HttpGet get = new HttpGet("/?q=something");
+
+ FutureCallback<HttpResponse> futureCallback = mock(FutureCallback.class);
+
+ // Timer hasn't been stopped prior to executing the request
+ verify(context, never()).stop();
+
+ Future<HttpResponse> responseFuture = asyncHttpClient.execute(host, get, futureCallback);
+
+ // Timer should still be running
+ verify(context, never()).stop();
+
+ try {
+ responseFuture.get(20, TimeUnit.SECONDS);
+ fail("This should fail as the client should not be able to connect");
+ } catch (Exception e) {
+ // Ignore
+ }
+ // After the computation is complete timer must be stopped
+ // Materialzing the future and calling the future callback is not an atomic operation so
+ // we need to wait for callback to succeed
+ verify(futureCallback, timeout(200).times(1)).failed(any(Exception.class));
+ verify(context, timeout(200).times(1)).stop();
+ }
+
+}
diff --git a/metrics-httpclient/pom.xml b/metrics-httpclient/pom.xml
index de14565..e3e565c 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-httpclient</artifactId>
@@ -16,16 +16,70 @@
durations and rates, and other useful information.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.httpclient</javaModuleName>
+ <http-client.version>4.5.14</http-client.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ <version>4.4.16</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
- <version>4.5.2</version>
+ <version>${http-client.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
</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 4b3b928..829011c 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
@@ -14,41 +14,37 @@ import static com.codahale.metrics.MetricRegistry.name;
public class HttpClientMetricNameStrategies {
public static final HttpClientMetricNameStrategy METHOD_ONLY =
- new HttpClientMetricNameStrategy() {
- @Override
- public String getNameFor(String name, HttpRequest request) {
- return name(HttpClient.class,
- name,
- methodNameString(request));
- }
- };
+ (name, request) -> name(HttpClient.class,
+ name,
+ methodNameString(request));
public static final HttpClientMetricNameStrategy HOST_AND_METHOD =
- new HttpClientMetricNameStrategy() {
- @Override
- public String getNameFor(String name, HttpRequest request) {
- return name(HttpClient.class,
- name,
- requestURI(request).getHost(),
- methodNameString(request));
- }
- };
+ (name, request) -> name(HttpClient.class,
+ name,
+ requestURI(request).getHost(),
+ methodNameString(request));
+
+ public static final HttpClientMetricNameStrategy PATH_AND_METHOD =
+ (name, request) -> {
+ final URIBuilder url = new URIBuilder(requestURI(request));
+ return name(HttpClient.class,
+ name,
+ url.getPath(),
+ methodNameString(request));
+ };
public static final HttpClientMetricNameStrategy QUERYLESS_URL_AND_METHOD =
- new HttpClientMetricNameStrategy() {
- @Override
- public String getNameFor(String name, HttpRequest request) {
- try {
- final URIBuilder url = new URIBuilder(requestURI(request));
- return name(HttpClient.class,
- name,
- url.removeQuery().build().toString(),
- methodNameString(request));
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e);
- }
- }
- };
+ (name, request) -> {
+ try {
+ final URIBuilder url = new URIBuilder(requestURI(request));
+ return name(HttpClient.class,
+ name,
+ url.removeQuery().build().toString(),
+ methodNameString(request));
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ };
private static String methodNameString(HttpRequest request) {
return request.getRequestLine().getMethod().toLowerCase() + "-requests";
@@ -60,6 +56,6 @@ public class HttpClientMetricNameStrategies {
return (request instanceof HttpUriRequest) ?
((HttpUriRequest) request).getURI() :
- URI.create(request.getRequestLine().getUri());
+ URI.create(request.getRequestLine().getUri());
}
}
diff --git a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategy.java b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategy.java
index a6400c2..08538e9 100644
--- a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategy.java
+++ b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategy.java
@@ -1,7 +1,17 @@
package com.codahale.metrics.httpclient;
+import com.codahale.metrics.MetricRegistry;
import org.apache.http.HttpRequest;
+import org.apache.http.client.HttpClient;
+@FunctionalInterface
public interface HttpClientMetricNameStrategy {
+
String getNameFor(String name, HttpRequest request);
+
+ default String getNameFor(String name, Exception exception) {
+ return MetricRegistry.name(HttpClient.class,
+ name,
+ exception.getClass().getSimpleName());
+ }
}
diff --git a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpClientConnectionManager.java b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpClientConnectionManager.java
index 0503780..89d3978 100644
--- a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpClientConnectionManager.java
+++ b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpClientConnectionManager.java
@@ -1,14 +1,19 @@
package com.codahale.metrics.httpclient;
-import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
-import org.apache.http.conn.*;
+import org.apache.http.conn.DnsResolver;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.HttpClientConnectionOperator;
+import org.apache.http.conn.HttpConnectionFactory;
+import org.apache.http.conn.ManagedHttpClientConnection;
+import org.apache.http.conn.SchemePortResolver;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.conn.DefaultHttpClientConnectionOperator;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
@@ -24,24 +29,36 @@ public class InstrumentedHttpClientConnectionManager extends PoolingHttpClientCo
protected static Registry<ConnectionSocketFactory> getDefaultRegistry() {
return RegistryBuilder.<ConnectionSocketFactory>create()
- .register("http", PlainConnectionSocketFactory.getSocketFactory())
- .register("https", SSLConnectionSocketFactory.getSocketFactory())
- .build();
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .register("https", SSLConnectionSocketFactory.getSocketFactory())
+ .build();
}
private final MetricRegistry metricsRegistry;
private final String name;
+ /**
+ * @deprecated Use {@link #builder(MetricRegistry)} instead.
+ */
+ @Deprecated
public InstrumentedHttpClientConnectionManager(MetricRegistry metricRegistry) {
this(metricRegistry, getDefaultRegistry());
}
+ /**
+ * @deprecated Use {@link #builder(MetricRegistry)} instead.
+ */
+ @Deprecated
public InstrumentedHttpClientConnectionManager(MetricRegistry metricsRegistry,
Registry<ConnectionSocketFactory> socketFactoryRegistry) {
this(metricsRegistry, socketFactoryRegistry, -1, TimeUnit.MILLISECONDS);
}
+ /**
+ * @deprecated Use {@link #builder(MetricRegistry)} instead.
+ */
+ @Deprecated
public InstrumentedHttpClientConnectionManager(MetricRegistry metricsRegistry,
Registry<ConnectionSocketFactory> socketFactoryRegistry,
long connTTL,
@@ -49,49 +66,55 @@ public class InstrumentedHttpClientConnectionManager extends PoolingHttpClientCo
this(metricsRegistry, socketFactoryRegistry, null, null, SystemDefaultDnsResolver.INSTANCE, connTTL, connTTLTimeUnit, null);
}
+
+ /**
+ * @deprecated Use {@link #builder(MetricRegistry)} instead.
+ */
+ @Deprecated
public InstrumentedHttpClientConnectionManager(MetricRegistry metricsRegistry,
Registry<ConnectionSocketFactory> socketFactoryRegistry,
- HttpConnectionFactory<HttpRoute,ManagedHttpClientConnection> connFactory,
+ HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection>
+ connFactory,
SchemePortResolver schemePortResolver,
DnsResolver dnsResolver,
long connTTL,
TimeUnit connTTLTimeUnit,
String name) {
- super(socketFactoryRegistry, connFactory, schemePortResolver, dnsResolver, connTTL, connTTLTimeUnit);
+ this(metricsRegistry,
+ new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver),
+ connFactory,
+ connTTL,
+ connTTLTimeUnit,
+ name);
+ }
+
+ /**
+ * @deprecated Use {@link #builder(MetricRegistry)} instead.
+ */
+ @Deprecated
+ public InstrumentedHttpClientConnectionManager(MetricRegistry metricsRegistry,
+ HttpClientConnectionOperator httpClientConnectionOperator,
+ HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection>
+ connFactory,
+ long connTTL,
+ TimeUnit connTTLTimeUnit,
+ String name) {
+ super(httpClientConnectionOperator, connFactory, connTTL, connTTLTimeUnit);
this.metricsRegistry = metricsRegistry;
this.name = name;
- metricsRegistry.register(name(HttpClientConnectionManager.class, name, "available-connections"),
- new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // this acquires a lock on the connection pool; remove if contention sucks
- return getTotalStats().getAvailable();
- }
- });
- metricsRegistry.register(name(HttpClientConnectionManager.class, name, "leased-connections"),
- new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // this acquires a lock on the connection pool; remove if contention sucks
- return getTotalStats().getLeased();
- }
- });
- metricsRegistry.register(name(HttpClientConnectionManager.class, name, "max-connections"),
- new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // this acquires a lock on the connection pool; remove if contention sucks
- return getTotalStats().getMax();
- }
- });
- metricsRegistry.register(name(HttpClientConnectionManager.class, name, "pending-connections"),
- new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // this acquires a lock on the connection pool; remove if contention sucks
- return getTotalStats().getPending();
- }
- });
+
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricsRegistry.registerGauge(name(HttpClientConnectionManager.class, name, "available-connections"),
+ () -> getTotalStats().getAvailable());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricsRegistry.registerGauge(name(HttpClientConnectionManager.class, name, "leased-connections"),
+ () -> getTotalStats().getLeased());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricsRegistry.registerGauge(name(HttpClientConnectionManager.class, name, "max-connections"),
+ () -> getTotalStats().getMax());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricsRegistry.registerGauge(name(HttpClientConnectionManager.class, name, "pending-connections"),
+ () -> getTotalStats().getPending());
}
@Override
@@ -102,4 +125,76 @@ public class InstrumentedHttpClientConnectionManager extends PoolingHttpClientCo
metricsRegistry.remove(name(HttpClientConnectionManager.class, name, "max-connections"));
metricsRegistry.remove(name(HttpClientConnectionManager.class, name, "pending-connections"));
}
+
+ public static Builder builder(MetricRegistry metricsRegistry) {
+ return new Builder().metricsRegistry(metricsRegistry);
+ }
+
+ public static class Builder {
+ private MetricRegistry metricsRegistry;
+ private HttpClientConnectionOperator httpClientConnectionOperator;
+ private Registry<ConnectionSocketFactory> socketFactoryRegistry = getDefaultRegistry();
+ private HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory;
+ private SchemePortResolver schemePortResolver;
+ private DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;
+ private long connTTL = -1;
+ private TimeUnit connTTLTimeUnit = TimeUnit.MILLISECONDS;
+ private String name;
+
+ Builder() {
+ }
+
+ public Builder metricsRegistry(MetricRegistry metricsRegistry) {
+ this.metricsRegistry = metricsRegistry;
+ return this;
+ }
+
+ public Builder socketFactoryRegistry(Registry<ConnectionSocketFactory> socketFactoryRegistry) {
+ this.socketFactoryRegistry = socketFactoryRegistry;
+ return this;
+ }
+
+ public Builder connFactory(HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
+ this.connFactory = connFactory;
+ return this;
+ }
+
+ public Builder schemePortResolver(SchemePortResolver schemePortResolver) {
+ this.schemePortResolver = schemePortResolver;
+ return this;
+ }
+
+ public Builder dnsResolver(DnsResolver dnsResolver) {
+ this.dnsResolver = dnsResolver;
+ return this;
+ }
+
+ public Builder connTTL(long connTTL) {
+ this.connTTL = connTTL;
+ return this;
+ }
+
+ public Builder connTTLTimeUnit(TimeUnit connTTLTimeUnit) {
+ this.connTTLTimeUnit = connTTLTimeUnit;
+ return this;
+ }
+
+ public Builder httpClientConnectionOperator(HttpClientConnectionOperator httpClientConnectionOperator) {
+ this.httpClientConnectionOperator = httpClientConnectionOperator;
+ return this;
+ }
+
+ public Builder name(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ public InstrumentedHttpClientConnectionManager build() {
+ if (httpClientConnectionOperator == null) {
+ httpClientConnectionOperator = new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver);
+ }
+ return new InstrumentedHttpClientConnectionManager(metricsRegistry, httpClientConnectionOperator, connFactory, connTTL, connTTLTimeUnit, name);
+ }
+ }
+
}
diff --git a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpClients.java b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpClients.java
index 30c6d7d..12c63a9 100644
--- a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpClients.java
+++ b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpClients.java
@@ -28,8 +28,7 @@ public class InstrumentedHttpClients {
HttpClientMetricNameStrategy metricNameStrategy) {
return HttpClientBuilder.create()
.setRequestExecutor(new InstrumentedHttpRequestExecutor(metricRegistry, metricNameStrategy))
- .setConnectionManager(new InstrumentedHttpClientConnectionManager(metricRegistry));
+ .setConnectionManager(InstrumentedHttpClientConnectionManager.builder(metricRegistry).build());
}
-
}
diff --git a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpRequestExecutor.java b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpRequestExecutor.java
index dedaccc..4acf024 100644
--- a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpRequestExecutor.java
+++ b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/InstrumentedHttpRequestExecutor.java
@@ -9,6 +9,8 @@ import org.apache.http.HttpResponse;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestExecutor;
+import com.codahale.metrics.Meter;
+
import java.io.IOException;
public class InstrumentedHttpRequestExecutor extends HttpRequestExecutor {
@@ -42,6 +44,9 @@ public class InstrumentedHttpRequestExecutor extends HttpRequestExecutor {
final Timer.Context timerContext = timer(request).time();
try {
return super.execute(request, conn, context);
+ } catch (HttpException | IOException e) {
+ meter(e).mark();
+ throw e;
} finally {
timerContext.stop();
}
@@ -50,4 +55,8 @@ public class InstrumentedHttpRequestExecutor extends HttpRequestExecutor {
private Timer timer(HttpRequest request) {
return registry.timer(metricNameStrategy.getNameFor(name, request));
}
+
+ private Meter meter(Exception e) {
+ return registry.meter(metricNameStrategy.getNameFor(name, e));
+ }
}
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 97942ae..5015b75 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
@@ -11,72 +11,98 @@ 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;
+import static com.codahale.metrics.httpclient.HttpClientMetricNameStrategies.HOST_AND_METHOD;
+import static com.codahale.metrics.httpclient.HttpClientMetricNameStrategies.METHOD_ONLY;
+import static com.codahale.metrics.httpclient.HttpClientMetricNameStrategies.PATH_AND_METHOD;
+import static com.codahale.metrics.httpclient.HttpClientMetricNameStrategies.QUERYLESS_URL_AND_METHOD;
+import static org.assertj.core.api.Assertions.assertThat;
public class HttpClientMetricNameStrategiesTest {
@Test
public void methodOnlyWithName() {
- assertThat(METHOD_ONLY.getNameFor("some-service", new HttpGet("/whatever")),
- is("org.apache.http.client.HttpClient.some-service.get-requests"));
+ assertThat(METHOD_ONLY.getNameFor("some-service", new HttpGet("/whatever")))
+ .isEqualTo("org.apache.http.client.HttpClient.some-service.get-requests");
}
@Test
public void methodOnlyWithoutName() {
- assertThat(METHOD_ONLY.getNameFor(null, new HttpGet("/whatever")),
- is("org.apache.http.client.HttpClient.get-requests"));
+ assertThat(METHOD_ONLY.getNameFor(null, new HttpGet("/whatever")))
+ .isEqualTo("org.apache.http.client.HttpClient.get-requests");
}
@Test
public void hostAndMethodWithName() {
- assertThat(HOST_AND_METHOD.getNameFor("some-service", new HttpPost("http://my.host.com/whatever")),
- is("org.apache.http.client.HttpClient.some-service.my.host.com.post-requests"));
+ assertThat(HOST_AND_METHOD.getNameFor("some-service", new HttpPost("http://my.host.com/whatever")))
+ .isEqualTo("org.apache.http.client.HttpClient.some-service.my.host.com.post-requests");
}
@Test
public void hostAndMethodWithoutName() {
- assertThat(HOST_AND_METHOD.getNameFor(null, new HttpPost("http://my.host.com/whatever")),
- is("org.apache.http.client.HttpClient.my.host.com.post-requests"));
+ assertThat(HOST_AND_METHOD.getNameFor(null, new HttpPost("http://my.host.com/whatever")))
+ .isEqualTo("org.apache.http.client.HttpClient.my.host.com.post-requests");
}
@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"));
+ assertThat(HOST_AND_METHOD.getNameFor("some-service", request))
+ .isEqualTo("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"));
+ assertThat(HOST_AND_METHOD.getNameFor(null, request))
+ .isEqualTo("org.apache.http.client.HttpClient.my.host.com.post-requests");
+ }
+
+ @Test
+ public void pathAndMethodWithName() {
+ assertThat(PATH_AND_METHOD.getNameFor("some-service", new HttpPost("http://my.host.com/whatever/happens")))
+ .isEqualTo("org.apache.http.client.HttpClient.some-service./whatever/happens.post-requests");
+ }
+
+ @Test
+ public void pathAndMethodWithoutName() {
+ assertThat(PATH_AND_METHOD.getNameFor(null, new HttpPost("http://my.host.com/whatever/happens")))
+ .isEqualTo("org.apache.http.client.HttpClient./whatever/happens.post-requests");
+ }
+
+ @Test
+ public void pathAndMethodWithNameInWrappedRequest() throws URISyntaxException {
+ HttpRequest request = rewriteRequestURI(new HttpPost("http://my.host.com/whatever/happens"));
+ assertThat(PATH_AND_METHOD.getNameFor("some-service", request))
+ .isEqualTo("org.apache.http.client.HttpClient.some-service./whatever/happens.post-requests");
+ }
+
+ @Test
+ public void pathAndMethodWithoutNameInWrappedRequest() throws URISyntaxException {
+ HttpRequest request = rewriteRequestURI(new HttpPost("http://my.host.com/whatever/happens"));
+ assertThat(PATH_AND_METHOD.getNameFor(null, request))
+ .isEqualTo("org.apache.http.client.HttpClient./whatever/happens.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"));
+ new HttpPut("https://thing.com:8090/my/path?ignore=this&and=this")))
+ .isEqualTo("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"));
+ assertThat(QUERYLESS_URL_AND_METHOD.getNameFor("some-service", request))
+ .isEqualTo("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);
+ URI uri = URIUtils.rewriteURI(wrapper.getURI(), null, URIUtils.DROP_FRAGMENT);
wrapper.setURI(uri);
return wrapper;
diff --git a/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/InstrumentedHttpClientConnectionManagerTest.java b/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/InstrumentedHttpClientConnectionManagerTest.java
index a0ca46d..662fa1a 100644
--- a/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/InstrumentedHttpClientConnectionManagerTest.java
+++ b/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/InstrumentedHttpClientConnectionManagerTest.java
@@ -1,9 +1,15 @@
package com.codahale.metrics.httpclient;
import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.Timer;
import org.junit.Assert;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.ArgumentMatchers.any;
public class InstrumentedHttpClientConnectionManagerTest {
@@ -11,13 +17,33 @@ public class InstrumentedHttpClientConnectionManagerTest {
@Test
public void shouldRemoveGauges() {
- final InstrumentedHttpClientConnectionManager instrumentedHttpClientConnectionManager = new InstrumentedHttpClientConnectionManager(metricRegistry);
- Assert.assertEquals(4, metricRegistry.getGauges().size());
+ final InstrumentedHttpClientConnectionManager instrumentedHttpClientConnectionManager = InstrumentedHttpClientConnectionManager.builder(metricRegistry).build();
+ assertThat(metricRegistry.getGauges().entrySet().stream()
+ .map(e -> entry(e.getKey(), e.getValue().getValue())))
+ .containsOnly(entry("org.apache.http.conn.HttpClientConnectionManager.available-connections", 0),
+ entry("org.apache.http.conn.HttpClientConnectionManager.leased-connections", 0),
+ entry("org.apache.http.conn.HttpClientConnectionManager.max-connections", 20),
+ entry("org.apache.http.conn.HttpClientConnectionManager.pending-connections", 0));
instrumentedHttpClientConnectionManager.close();
Assert.assertEquals(0, metricRegistry.getGauges().size());
// should be able to create another one with the same name ("")
- new InstrumentedHttpClientConnectionManager(metricRegistry);
+ InstrumentedHttpClientConnectionManager.builder(metricRegistry).build().close();
+ }
+
+ @Test
+ public void configurableViaBuilder() {
+ final MetricRegistry registry = Mockito.mock(MetricRegistry.class);
+
+ InstrumentedHttpClientConnectionManager.builder(registry)
+ .name("some-name")
+ .name("some-other-name")
+ .build()
+ .close();
+
+ ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
+ Mockito.verify(registry, Mockito.atLeast(1)).registerGauge(argumentCaptor.capture(), any());
+ assertTrue(argumentCaptor.getValue().contains("some-other-name"));
}
}
diff --git a/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/InstrumentedHttpClientsTest.java b/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/InstrumentedHttpClientsTest.java
index b77bc01..9d6c147 100644
--- a/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/InstrumentedHttpClientsTest.java
+++ b/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/InstrumentedHttpClientsTest.java
@@ -3,16 +3,24 @@ package com.codahale.metrics.httpclient;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricRegistryListener;
import com.codahale.metrics.Timer;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
import org.apache.http.HttpRequest;
+import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.junit.Before;
import org.junit.Test;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import java.net.InetSocketAddress;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class InstrumentedHttpClientsTest {
private final HttpClientMetricNameStrategy metricNameStrategy =
@@ -21,10 +29,10 @@ public class InstrumentedHttpClientsTest {
mock(MetricRegistryListener.class);
private final MetricRegistry metricRegistry = new MetricRegistry();
private final HttpClient client =
- InstrumentedHttpClients.createDefault(metricRegistry, metricNameStrategy);
+ InstrumentedHttpClients.custom(metricRegistry, metricNameStrategy).disableAutomaticRetries().build();
@Before
- public void setUp() throws Exception {
+ public void setUp() {
metricRegistry.addListener(registryListener);
}
@@ -33,11 +41,37 @@ public class InstrumentedHttpClientsTest {
final HttpGet get = new HttpGet("http://example.com?q=anything");
final String metricName = "some.made.up.metric.name";
- when(metricNameStrategy.getNameFor(anyString(), any(HttpRequest.class)))
+ when(metricNameStrategy.getNameFor(any(), any(HttpRequest.class)))
.thenReturn(metricName);
client.execute(get);
verify(registryListener).onTimerAdded(eq(metricName), any(Timer.class));
}
+
+ @Test
+ public void registersExpectedExceptionMetrics() throws Exception {
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(0), 0);
+
+ final HttpGet get = new HttpGet("http://localhost:" + httpServer.getAddress().getPort() + "/");
+ final String requestMetricName = "request";
+ final String exceptionMetricName = "exception";
+
+ httpServer.createContext("/", HttpExchange::close);
+ httpServer.start();
+
+ when(metricNameStrategy.getNameFor(any(), any(HttpRequest.class)))
+ .thenReturn(requestMetricName);
+ when(metricNameStrategy.getNameFor(any(), any(Exception.class)))
+ .thenReturn(exceptionMetricName);
+
+ try {
+ client.execute(get);
+ fail();
+ } catch (NoHttpResponseException expected) {
+ assertThat(metricRegistry.getMeters()).containsKey("exception");
+ } finally {
+ httpServer.stop(0);
+ }
+ }
}
diff --git a/metrics-httpclient5/pom.xml b/metrics-httpclient5/pom.xml
new file mode 100644
index 0000000..bbb1ec1
--- /dev/null
+++ b/metrics-httpclient5/pom.xml
@@ -0,0 +1,101 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-httpclient5</artifactId>
+ <name>Metrics Integration for Apache HttpClient 5.x</name>
+ <packaging>bundle</packaging>
+ <description>
+ An Apache HttpClient 5.x wrapper providing Metrics instrumentation of connection pools, request
+ durations and rates, and other useful information.
+ </description>
+
+ <properties>
+ <javaModuleName>com.codahale.metrics.httpclient</javaModuleName>
+ <http-client.version>5.3.1</http-client.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents.client5</groupId>
+ <artifactId>httpclient5</artifactId>
+ <version>${http-client.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents.core5</groupId>
+ <artifactId>httpcore5</artifactId>
+ <version>5.2.4</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents.client5</groupId>
+ <artifactId>httpclient5</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents.core5</groupId>
+ <artifactId>httpcore5</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <version>4.2.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategies.java b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategies.java
new file mode 100644
index 0000000..a7911a2
--- /dev/null
+++ b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategies.java
@@ -0,0 +1,48 @@
+package com.codahale.metrics.httpclient5;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.net.URIBuilder;
+
+import java.net.URISyntaxException;
+import java.util.Locale;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+public class HttpClientMetricNameStrategies {
+
+ public static final HttpClientMetricNameStrategy METHOD_ONLY =
+ (name, request) -> name(HttpClient.class,
+ name,
+ methodNameString(request));
+
+ public static final HttpClientMetricNameStrategy HOST_AND_METHOD =
+ (name, request) -> {
+ try {
+ return name(HttpClient.class,
+ name,
+ request.getUri().getHost(),
+ methodNameString(request));
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ };
+
+ public static final HttpClientMetricNameStrategy QUERYLESS_URL_AND_METHOD =
+ (name, request) -> {
+ try {
+ final URIBuilder url = new URIBuilder(request.getUri());
+ return name(HttpClient.class,
+ name,
+ url.removeQuery().build().toString(),
+ methodNameString(request));
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ };
+
+ private static String methodNameString(HttpRequest request) {
+ return request.getMethod().toLowerCase(Locale.ROOT) + "-requests";
+ }
+
+}
diff --git a/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategy.java b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategy.java
new file mode 100644
index 0000000..2077ef0
--- /dev/null
+++ b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategy.java
@@ -0,0 +1,17 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.HttpRequest;
+
+@FunctionalInterface
+public interface HttpClientMetricNameStrategy {
+
+ String getNameFor(String name, HttpRequest request);
+
+ default String getNameFor(String name, Exception exception) {
+ return MetricRegistry.name(HttpClient.class,
+ name,
+ exception.getClass().getSimpleName());
+ }
+}
diff --git a/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedAsyncClientConnectionManager.java b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedAsyncClientConnectionManager.java
new file mode 100644
index 0000000..b778c54
--- /dev/null
+++ b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedAsyncClientConnectionManager.java
@@ -0,0 +1,155 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.hc.client5.http.DnsResolver;
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
+import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http.config.Registry;
+import org.apache.hc.core5.http.config.RegistryBuilder;
+import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
+import org.apache.hc.core5.pool.PoolReusePolicy;
+import org.apache.hc.core5.util.TimeValue;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A {@link HttpClientConnectionManager} which monitors the number of open connections.
+ */
+public class InstrumentedAsyncClientConnectionManager extends PoolingAsyncClientConnectionManager {
+ private static final String METRICS_PREFIX = AsyncClientConnectionManager.class.getName();
+
+ protected static Registry<TlsStrategy> getDefaultTlsStrategy() {
+ return RegistryBuilder.<TlsStrategy>create()
+ .register(URIScheme.HTTPS.id, DefaultClientTlsStrategy.getDefault())
+ .build();
+ }
+
+ private final MetricRegistry metricsRegistry;
+ private final String name;
+
+ InstrumentedAsyncClientConnectionManager(final MetricRegistry metricRegistry,
+ final String name,
+ final Lookup<TlsStrategy> tlsStrategyLookup,
+ final PoolConcurrencyPolicy poolConcurrencyPolicy,
+ final PoolReusePolicy poolReusePolicy,
+ final TimeValue timeToLive,
+ final SchemePortResolver schemePortResolver,
+ final DnsResolver dnsResolver) {
+
+ super(tlsStrategyLookup, poolConcurrencyPolicy, poolReusePolicy, timeToLive, schemePortResolver, dnsResolver);
+ this.metricsRegistry = requireNonNull(metricRegistry, "metricRegistry");
+ this.name = name;
+
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(METRICS_PREFIX, name, "available-connections"),
+ () -> getTotalStats().getAvailable());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(METRICS_PREFIX, name, "leased-connections"),
+ () -> getTotalStats().getLeased());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(METRICS_PREFIX, name, "max-connections"),
+ () -> getTotalStats().getMax());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(METRICS_PREFIX, name, "pending-connections"),
+ () -> getTotalStats().getPending());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() {
+ close(CloseMode.GRACEFUL);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close(CloseMode closeMode) {
+ super.close(closeMode);
+ metricsRegistry.remove(name(METRICS_PREFIX, name, "available-connections"));
+ metricsRegistry.remove(name(METRICS_PREFIX, name, "leased-connections"));
+ metricsRegistry.remove(name(METRICS_PREFIX, name, "max-connections"));
+ metricsRegistry.remove(name(METRICS_PREFIX, name, "pending-connections"));
+ }
+
+ public static Builder builder(MetricRegistry metricsRegistry) {
+ return new Builder().metricsRegistry(metricsRegistry);
+ }
+
+ public static class Builder {
+ private MetricRegistry metricsRegistry;
+ private String name;
+ private Lookup<TlsStrategy> tlsStrategyLookup = getDefaultTlsStrategy();
+ private SchemePortResolver schemePortResolver;
+ private DnsResolver dnsResolver;
+ private PoolConcurrencyPolicy poolConcurrencyPolicy;
+ private PoolReusePolicy poolReusePolicy;
+ private TimeValue timeToLive = TimeValue.NEG_ONE_MILLISECOND;
+
+ Builder() {
+ }
+
+ public Builder metricsRegistry(MetricRegistry metricRegistry) {
+ this.metricsRegistry = requireNonNull(metricRegistry, "metricRegistry");
+ return this;
+ }
+
+ public Builder name(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder schemePortResolver(SchemePortResolver schemePortResolver) {
+ this.schemePortResolver = schemePortResolver;
+ return this;
+ }
+
+ public Builder dnsResolver(DnsResolver dnsResolver) {
+ this.dnsResolver = dnsResolver;
+ return this;
+ }
+
+ public Builder timeToLive(TimeValue timeToLive) {
+ this.timeToLive = timeToLive;
+ return this;
+ }
+
+ public Builder tlsStrategyLookup(Lookup<TlsStrategy> tlsStrategyLookup) {
+ this.tlsStrategyLookup = tlsStrategyLookup;
+ return this;
+ }
+
+ public Builder poolConcurrencyPolicy(PoolConcurrencyPolicy poolConcurrencyPolicy) {
+ this.poolConcurrencyPolicy = poolConcurrencyPolicy;
+ return this;
+ }
+
+ public Builder poolReusePolicy(PoolReusePolicy poolReusePolicy) {
+ this.poolReusePolicy = poolReusePolicy;
+ return this;
+ }
+
+ public InstrumentedAsyncClientConnectionManager build() {
+ return new InstrumentedAsyncClientConnectionManager(
+ metricsRegistry,
+ name,
+ tlsStrategyLookup,
+ poolConcurrencyPolicy,
+ poolReusePolicy,
+ timeToLive,
+ schemePortResolver,
+ dnsResolver);
+ }
+ }
+
+}
diff --git a/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedAsyncExecChainHandler.java b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedAsyncExecChainHandler.java
new file mode 100644
index 0000000..f99b228
--- /dev/null
+++ b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedAsyncExecChainHandler.java
@@ -0,0 +1,99 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.apache.hc.client5.http.async.AsyncExecCallback;
+import org.apache.hc.client5.http.async.AsyncExecChain;
+import org.apache.hc.client5.http.async.AsyncExecChainHandler;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+
+import java.io.IOException;
+
+import static java.util.Objects.requireNonNull;
+
+class InstrumentedAsyncExecChainHandler implements AsyncExecChainHandler {
+ private final MetricRegistry registry;
+ private final HttpClientMetricNameStrategy metricNameStrategy;
+ private final String name;
+
+ public InstrumentedAsyncExecChainHandler(MetricRegistry registry, HttpClientMetricNameStrategy metricNameStrategy) {
+ this(registry, metricNameStrategy, null);
+ }
+
+ public InstrumentedAsyncExecChainHandler(MetricRegistry registry,
+ HttpClientMetricNameStrategy metricNameStrategy,
+ String name) {
+ this.registry = requireNonNull(registry, "registry");
+ this.metricNameStrategy = requireNonNull(metricNameStrategy, "metricNameStrategy");
+ this.name = name;
+ }
+
+ @Override
+ public void execute(HttpRequest request,
+ AsyncEntityProducer entityProducer,
+ AsyncExecChain.Scope scope,
+ AsyncExecChain chain,
+ AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
+ final InstrumentedAsyncExecCallback instrumentedAsyncExecCallback =
+ new InstrumentedAsyncExecCallback(registry, metricNameStrategy, name, asyncExecCallback, request);
+ chain.proceed(request, entityProducer, scope, instrumentedAsyncExecCallback);
+
+ }
+
+ final static class InstrumentedAsyncExecCallback implements AsyncExecCallback {
+ private final MetricRegistry registry;
+ private final HttpClientMetricNameStrategy metricNameStrategy;
+ private final String name;
+ private final AsyncExecCallback delegate;
+ private final Timer.Context timerContext;
+
+ public InstrumentedAsyncExecCallback(MetricRegistry registry,
+ HttpClientMetricNameStrategy metricNameStrategy,
+ String name,
+ AsyncExecCallback delegate,
+ HttpRequest request) {
+ this.registry = registry;
+ this.metricNameStrategy = metricNameStrategy;
+ this.name = name;
+ this.delegate = delegate;
+ this.timerContext = timer(request).time();
+ }
+
+ @Override
+ public AsyncDataConsumer handleResponse(HttpResponse response, EntityDetails entityDetails) throws HttpException, IOException {
+ return delegate.handleResponse(response, entityDetails);
+ }
+
+ @Override
+ public void handleInformationResponse(HttpResponse response) throws HttpException, IOException {
+ delegate.handleInformationResponse(response);
+ }
+
+ @Override
+ public void completed() {
+ delegate.completed();
+ timerContext.stop();
+ }
+
+ @Override
+ public void failed(Exception cause) {
+ delegate.failed(cause);
+ meter(cause).mark();
+ timerContext.stop();
+ }
+
+ private Timer timer(HttpRequest request) {
+ return registry.timer(metricNameStrategy.getNameFor(name, request));
+ }
+
+ private Meter meter(Exception e) {
+ return registry.meter(metricNameStrategy.getNameFor(name, e));
+ }
+ }
+}
diff --git a/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpAsyncClients.java b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpAsyncClients.java
new file mode 100644
index 0000000..0bda99d
--- /dev/null
+++ b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpAsyncClients.java
@@ -0,0 +1,43 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.hc.client5.http.impl.ChainElement;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
+
+import static com.codahale.metrics.httpclient5.HttpClientMetricNameStrategies.METHOD_ONLY;
+
+public class InstrumentedHttpAsyncClients {
+ private InstrumentedHttpAsyncClients() {
+ super();
+ }
+
+ public static CloseableHttpAsyncClient createDefault(MetricRegistry metricRegistry) {
+ return createDefault(metricRegistry, METHOD_ONLY);
+ }
+
+ public static CloseableHttpAsyncClient createDefault(MetricRegistry metricRegistry,
+ HttpClientMetricNameStrategy metricNameStrategy) {
+ return custom(metricRegistry, metricNameStrategy).build();
+ }
+
+ public static HttpAsyncClientBuilder custom(MetricRegistry metricRegistry) {
+ return custom(metricRegistry, METHOD_ONLY);
+ }
+
+ public static HttpAsyncClientBuilder custom(MetricRegistry metricRegistry,
+ HttpClientMetricNameStrategy metricNameStrategy) {
+ return custom(metricRegistry, metricNameStrategy, InstrumentedAsyncClientConnectionManager.builder(metricRegistry).build());
+ }
+
+ public static HttpAsyncClientBuilder custom(MetricRegistry metricRegistry,
+ HttpClientMetricNameStrategy metricNameStrategy,
+ AsyncClientConnectionManager clientConnectionManager) {
+ return HttpAsyncClientBuilder.create()
+ .setConnectionManager(clientConnectionManager)
+ .addExecInterceptorBefore(ChainElement.CONNECT.name(), "dropwizard-metrics",
+ new InstrumentedAsyncExecChainHandler(metricRegistry, metricNameStrategy));
+ }
+
+}
diff --git a/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientConnectionManager.java b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientConnectionManager.java
new file mode 100644
index 0000000..c98b97f
--- /dev/null
+++ b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientConnectionManager.java
@@ -0,0 +1,178 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.hc.client5.http.DnsResolver;
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
+import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
+import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
+import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
+import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.Registry;
+import org.apache.hc.core5.http.config.RegistryBuilder;
+import org.apache.hc.core5.http.io.HttpConnectionFactory;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
+import org.apache.hc.core5.pool.PoolReusePolicy;
+import org.apache.hc.core5.util.TimeValue;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A {@link HttpClientConnectionManager} which monitors the number of open connections.
+ */
+public class InstrumentedHttpClientConnectionManager extends PoolingHttpClientConnectionManager {
+ private static final String METRICS_PREFIX = HttpClientConnectionManager.class.getName();
+
+ protected static Registry<ConnectionSocketFactory> getDefaultRegistry() {
+ return RegistryBuilder.<ConnectionSocketFactory>create()
+ .register(URIScheme.HTTP.id, PlainConnectionSocketFactory.getSocketFactory())
+ .register(URIScheme.HTTPS.id, SSLConnectionSocketFactory.getSocketFactory())
+ .build();
+ }
+
+ private final MetricRegistry metricsRegistry;
+ private final String name;
+
+ InstrumentedHttpClientConnectionManager(final MetricRegistry metricRegistry,
+ final String name,
+ final HttpClientConnectionOperator httpClientConnectionOperator,
+ final PoolConcurrencyPolicy poolConcurrencyPolicy,
+ final PoolReusePolicy poolReusePolicy,
+ final TimeValue timeToLive,
+ final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
+
+ super(httpClientConnectionOperator, poolConcurrencyPolicy, poolReusePolicy, timeToLive, connFactory);
+ this.metricsRegistry = requireNonNull(metricRegistry, "metricRegistry");
+ this.name = name;
+
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(METRICS_PREFIX, name, "available-connections"),
+ () -> {
+ return getTotalStats().getAvailable();
+ });
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(METRICS_PREFIX, name, "leased-connections"),
+ () -> getTotalStats().getLeased());
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(METRICS_PREFIX, name, "max-connections"),
+ () -> getTotalStats().getMax()
+ );
+ // this acquires a lock on the connection pool; remove if contention sucks
+ metricRegistry.registerGauge(name(METRICS_PREFIX, name, "pending-connections"),
+ () -> getTotalStats().getPending());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() {
+ close(CloseMode.GRACEFUL);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close(CloseMode closeMode) {
+ super.close(closeMode);
+ metricsRegistry.remove(name(METRICS_PREFIX, name, "available-connections"));
+ metricsRegistry.remove(name(METRICS_PREFIX, name, "leased-connections"));
+ metricsRegistry.remove(name(METRICS_PREFIX, name, "max-connections"));
+ metricsRegistry.remove(name(METRICS_PREFIX, name, "pending-connections"));
+ }
+
+ public static Builder builder(MetricRegistry metricsRegistry) {
+ return new Builder().metricsRegistry(metricsRegistry);
+ }
+
+ public static class Builder {
+ private MetricRegistry metricsRegistry;
+ private String name;
+ private HttpClientConnectionOperator httpClientConnectionOperator;
+ private Registry<ConnectionSocketFactory> socketFactoryRegistry = getDefaultRegistry();
+ private SchemePortResolver schemePortResolver;
+ private DnsResolver dnsResolver;
+ private PoolConcurrencyPolicy poolConcurrencyPolicy;
+ private PoolReusePolicy poolReusePolicy;
+ private TimeValue timeToLive = TimeValue.NEG_ONE_MILLISECOND;
+ private HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
+
+ Builder() {
+ }
+
+ public Builder metricsRegistry(MetricRegistry metricRegistry) {
+ this.metricsRegistry = requireNonNull(metricRegistry, "metricRegistry");
+ return this;
+ }
+
+ public Builder name(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder socketFactoryRegistry(Registry<ConnectionSocketFactory> socketFactoryRegistry) {
+ this.socketFactoryRegistry = requireNonNull(socketFactoryRegistry, "socketFactoryRegistry");
+ return this;
+ }
+
+ public Builder connFactory(HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
+ this.connFactory = connFactory;
+ return this;
+ }
+
+ public Builder schemePortResolver(SchemePortResolver schemePortResolver) {
+ this.schemePortResolver = schemePortResolver;
+ return this;
+ }
+
+ public Builder dnsResolver(DnsResolver dnsResolver) {
+ this.dnsResolver = dnsResolver;
+ return this;
+ }
+
+ public Builder timeToLive(TimeValue timeToLive) {
+ this.timeToLive = timeToLive;
+ return this;
+ }
+
+ public Builder httpClientConnectionOperator(HttpClientConnectionOperator httpClientConnectionOperator) {
+ this.httpClientConnectionOperator = httpClientConnectionOperator;
+ return this;
+ }
+
+ public Builder poolConcurrencyPolicy(PoolConcurrencyPolicy poolConcurrencyPolicy) {
+ this.poolConcurrencyPolicy = poolConcurrencyPolicy;
+ return this;
+ }
+
+ public Builder poolReusePolicy(PoolReusePolicy poolReusePolicy) {
+ this.poolReusePolicy = poolReusePolicy;
+ return this;
+ }
+
+ public InstrumentedHttpClientConnectionManager build() {
+ if (httpClientConnectionOperator == null) {
+ httpClientConnectionOperator = new DefaultHttpClientConnectionOperator(
+ socketFactoryRegistry,
+ schemePortResolver,
+ dnsResolver);
+ }
+
+ return new InstrumentedHttpClientConnectionManager(
+ metricsRegistry,
+ name,
+ httpClientConnectionOperator,
+ poolConcurrencyPolicy,
+ poolReusePolicy,
+ timeToLive,
+ connFactory);
+ }
+ }
+}
diff --git a/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpClients.java b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpClients.java
new file mode 100644
index 0000000..f8f90f2
--- /dev/null
+++ b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpClients.java
@@ -0,0 +1,34 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+
+import static com.codahale.metrics.httpclient5.HttpClientMetricNameStrategies.METHOD_ONLY;
+
+public class InstrumentedHttpClients {
+ private InstrumentedHttpClients() {
+ super();
+ }
+
+ public static CloseableHttpClient createDefault(MetricRegistry metricRegistry) {
+ return createDefault(metricRegistry, METHOD_ONLY);
+ }
+
+ public static CloseableHttpClient createDefault(MetricRegistry metricRegistry,
+ HttpClientMetricNameStrategy metricNameStrategy) {
+ return custom(metricRegistry, metricNameStrategy).build();
+ }
+
+ public static HttpClientBuilder custom(MetricRegistry metricRegistry) {
+ return custom(metricRegistry, METHOD_ONLY);
+ }
+
+ public static HttpClientBuilder custom(MetricRegistry metricRegistry,
+ HttpClientMetricNameStrategy metricNameStrategy) {
+ return HttpClientBuilder.create()
+ .setRequestExecutor(new InstrumentedHttpRequestExecutor(metricRegistry, metricNameStrategy))
+ .setConnectionManager(InstrumentedHttpClientConnectionManager.builder(metricRegistry).build());
+ }
+
+}
diff --git a/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpRequestExecutor.java b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpRequestExecutor.java
new file mode 100644
index 0000000..5ffc465
--- /dev/null
+++ b/metrics-httpclient5/src/main/java/com/codahale/metrics/httpclient5/InstrumentedHttpRequestExecutor.java
@@ -0,0 +1,78 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ConnectionReuseStrategy;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.impl.Http1StreamListener;
+import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
+import org.apache.hc.core5.http.io.HttpClientConnection;
+import org.apache.hc.core5.http.io.HttpResponseInformationCallback;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Timeout;
+
+import java.io.IOException;
+
+public class InstrumentedHttpRequestExecutor extends HttpRequestExecutor {
+ private final MetricRegistry registry;
+ private final HttpClientMetricNameStrategy metricNameStrategy;
+ private final String name;
+
+ public InstrumentedHttpRequestExecutor(MetricRegistry registry,
+ HttpClientMetricNameStrategy metricNameStrategy) {
+ this(registry, metricNameStrategy, null);
+ }
+
+ public InstrumentedHttpRequestExecutor(MetricRegistry registry,
+ HttpClientMetricNameStrategy metricNameStrategy,
+ String name) {
+ this(registry, metricNameStrategy, name, HttpRequestExecutor.DEFAULT_WAIT_FOR_CONTINUE);
+ }
+
+ public InstrumentedHttpRequestExecutor(MetricRegistry registry,
+ HttpClientMetricNameStrategy metricNameStrategy,
+ String name,
+ Timeout waitForContinue) {
+ this(registry, metricNameStrategy, name, waitForContinue, null, null);
+ }
+
+ public InstrumentedHttpRequestExecutor(MetricRegistry registry,
+ HttpClientMetricNameStrategy metricNameStrategy,
+ String name,
+ Timeout waitForContinue,
+ ConnectionReuseStrategy connReuseStrategy,
+ Http1StreamListener streamListener) {
+ super(waitForContinue, connReuseStrategy, streamListener);
+ this.registry = registry;
+ this.name = name;
+ this.metricNameStrategy = metricNameStrategy;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ClassicHttpResponse execute(ClassicHttpRequest request, HttpClientConnection conn, HttpResponseInformationCallback informationCallback, HttpContext context) throws IOException, HttpException {
+ final Timer.Context timerContext = timer(request).time();
+ try {
+ return super.execute(request, conn, informationCallback, context);
+ } catch (HttpException | IOException e) {
+ meter(e).mark();
+ throw e;
+ } finally {
+ timerContext.stop();
+ }
+ }
+
+ private Timer timer(HttpRequest request) {
+ return registry.timer(metricNameStrategy.getNameFor(name, request));
+ }
+
+ private Meter meter(Exception e) {
+ return registry.meter(metricNameStrategy.getNameFor(name, e));
+ }
+}
diff --git a/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategiesTest.java b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategiesTest.java
new file mode 100644
index 0000000..25fa771
--- /dev/null
+++ b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/HttpClientMetricNameStrategiesTest.java
@@ -0,0 +1,82 @@
+package com.codahale.metrics.httpclient5;
+
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.classic.methods.HttpPut;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.message.HttpRequestWrapper;
+import org.apache.hc.core5.net.URIBuilder;
+import org.junit.Test;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import static com.codahale.metrics.httpclient5.HttpClientMetricNameStrategies.HOST_AND_METHOD;
+import static com.codahale.metrics.httpclient5.HttpClientMetricNameStrategies.METHOD_ONLY;
+import static com.codahale.metrics.httpclient5.HttpClientMetricNameStrategies.QUERYLESS_URL_AND_METHOD;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class HttpClientMetricNameStrategiesTest {
+
+ @Test
+ public void methodOnlyWithName() {
+ assertThat(METHOD_ONLY.getNameFor("some-service", new HttpGet("/whatever")))
+ .isEqualTo("org.apache.hc.client5.http.classic.HttpClient.some-service.get-requests");
+ }
+
+ @Test
+ public void methodOnlyWithoutName() {
+ assertThat(METHOD_ONLY.getNameFor(null, new HttpGet("/whatever")))
+ .isEqualTo("org.apache.hc.client5.http.classic.HttpClient.get-requests");
+ }
+
+ @Test
+ public void hostAndMethodWithName() {
+ assertThat(HOST_AND_METHOD.getNameFor("some-service", new HttpPost("http://my.host.com/whatever")))
+ .isEqualTo("org.apache.hc.client5.http.classic.HttpClient.some-service.my.host.com.post-requests");
+ }
+
+ @Test
+ public void hostAndMethodWithoutName() {
+ assertThat(HOST_AND_METHOD.getNameFor(null, new HttpPost("http://my.host.com/whatever")))
+ .isEqualTo("org.apache.hc.client5.http.classic.HttpClient.my.host.com.post-requests");
+ }
+
+ @Test
+ public void hostAndMethodWithNameInWrappedRequest() throws URISyntaxException {
+ HttpRequest request = rewriteRequestURI(new HttpPost("http://my.host.com/whatever"));
+
+ assertThat(HOST_AND_METHOD.getNameFor("some-service", request))
+ .isEqualTo("org.apache.hc.client5.http.classic.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))
+ .isEqualTo("org.apache.hc.client5.http.classic.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")))
+ .isEqualTo("org.apache.hc.client5.http.classic.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))
+ .isEqualTo("org.apache.hc.client5.http.classic.HttpClient.some-service.https://thing.com:8090/my/path.put-requests");
+ }
+
+ private static HttpRequest rewriteRequestURI(HttpRequest request) throws URISyntaxException {
+ URI uri = new URIBuilder(request.getUri()).setFragment(null).build();
+ HttpRequestWrapper wrapper = new HttpRequestWrapper(request);
+ wrapper.setUri(uri);
+
+ return wrapper;
+ }
+}
diff --git a/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedAsyncClientConnectionManagerTest.java b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedAsyncClientConnectionManagerTest.java
new file mode 100644
index 0000000..7931692
--- /dev/null
+++ b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedAsyncClientConnectionManagerTest.java
@@ -0,0 +1,48 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.ArgumentMatchers.any;
+
+public class InstrumentedAsyncClientConnectionManagerTest {
+ private final MetricRegistry metricRegistry = new MetricRegistry();
+
+ @Test
+ public void shouldRemoveGauges() {
+ final InstrumentedAsyncClientConnectionManager instrumentedHttpClientConnectionManager = InstrumentedAsyncClientConnectionManager.builder(metricRegistry).build();
+ assertThat(metricRegistry.getGauges().entrySet().stream()
+ .map(e -> entry(e.getKey(), e.getValue().getValue())))
+ .containsOnly(entry("org.apache.hc.client5.http.nio.AsyncClientConnectionManager.available-connections", 0),
+ entry("org.apache.hc.client5.http.nio.AsyncClientConnectionManager.leased-connections", 0),
+ entry("org.apache.hc.client5.http.nio.AsyncClientConnectionManager.max-connections", 25),
+ entry("org.apache.hc.client5.http.nio.AsyncClientConnectionManager.pending-connections", 0));
+
+ instrumentedHttpClientConnectionManager.close();
+ Assert.assertEquals(0, metricRegistry.getGauges().size());
+
+ // should be able to create another one with the same name ("")
+ InstrumentedHttpClientConnectionManager.builder(metricRegistry).build().close();
+ }
+
+ @Test
+ public void configurableViaBuilder() {
+ final MetricRegistry registry = Mockito.mock(MetricRegistry.class);
+
+ InstrumentedAsyncClientConnectionManager.builder(registry)
+ .name("some-name")
+ .name("some-other-name")
+ .build()
+ .close();
+
+ ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
+ Mockito.verify(registry, Mockito.atLeast(1)).registerGauge(argumentCaptor.capture(), any());
+ assertTrue(argumentCaptor.getValue().contains("some-other-name"));
+ }
+}
diff --git a/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpAsyncClientsTest.java b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpAsyncClientsTest.java
new file mode 100644
index 0000000..ebef1f2
--- /dev/null
+++ b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpAsyncClientsTest.java
@@ -0,0 +1,203 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.MetricRegistryListener;
+import com.codahale.metrics.Timer;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.ConnectionClosedException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class InstrumentedHttpAsyncClientsTest {
+ @Rule
+ public final MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private HttpClientMetricNameStrategy metricNameStrategy;
+ @Mock
+ private MetricRegistryListener registryListener;
+ private HttpServer httpServer;
+ private MetricRegistry metricRegistry;
+ private CloseableHttpAsyncClient client;
+
+ @Before
+ public void setUp() throws IOException {
+ httpServer = HttpServer.create(new InetSocketAddress(0), 0);
+
+ metricRegistry = new MetricRegistry();
+ metricRegistry.addListener(registryListener);
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ if (client != null) {
+ client.close();
+ }
+ if (httpServer != null) {
+ httpServer.stop(0);
+ }
+ }
+
+ @Test
+ public void registersExpectedMetricsGivenNameStrategy() throws Exception {
+ client = InstrumentedHttpAsyncClients.custom(metricRegistry, metricNameStrategy).disableAutomaticRetries().build();
+ client.start();
+
+ final SimpleHttpRequest request = SimpleRequestBuilder
+ .get("http://localhost:" + httpServer.getAddress().getPort() + "/")
+ .build();
+ final String metricName = "some.made.up.metric.name";
+
+ httpServer.createContext("/", exchange -> {
+ exchange.sendResponseHeaders(200, 0L);
+ exchange.setStreams(null, null);
+ exchange.getResponseBody().write("TEST".getBytes(StandardCharsets.US_ASCII));
+ exchange.close();
+ });
+ httpServer.start();
+
+ when(metricNameStrategy.getNameFor(any(), any(HttpRequest.class))).thenReturn(metricName);
+
+ final Future<SimpleHttpResponse> responseFuture = client.execute(request, new FutureCallback<SimpleHttpResponse>() {
+ @Override
+ public void completed(SimpleHttpResponse result) {
+ assertThat(result.getBodyText()).isEqualTo("TEST");
+ }
+
+ @Override
+ public void failed(Exception ex) {
+ fail();
+ }
+
+ @Override
+ public void cancelled() {
+ fail();
+ }
+ });
+ responseFuture.get(1L, TimeUnit.SECONDS);
+
+ verify(registryListener).onTimerAdded(eq(metricName), any(Timer.class));
+ }
+
+ @Test
+ public void registersExpectedExceptionMetrics() throws Exception {
+ client = InstrumentedHttpAsyncClients.custom(metricRegistry, metricNameStrategy).disableAutomaticRetries().build();
+ client.start();
+
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final SimpleHttpRequest request = SimpleRequestBuilder
+ .get("http://localhost:" + httpServer.getAddress().getPort() + "/")
+ .build();
+ final String requestMetricName = "request";
+ final String exceptionMetricName = "exception";
+
+ httpServer.createContext("/", HttpExchange::close);
+ httpServer.start();
+
+ when(metricNameStrategy.getNameFor(any(), any(HttpRequest.class)))
+ .thenReturn(requestMetricName);
+ when(metricNameStrategy.getNameFor(any(), any(Exception.class)))
+ .thenReturn(exceptionMetricName);
+
+ try {
+ final Future<SimpleHttpResponse> responseFuture = client.execute(request, new FutureCallback<SimpleHttpResponse>() {
+ @Override
+ public void completed(SimpleHttpResponse result) {
+ fail();
+ }
+
+ @Override
+ public void failed(Exception ex) {
+ countDownLatch.countDown();
+ }
+
+ @Override
+ public void cancelled() {
+ fail();
+ }
+ });
+ countDownLatch.await(5, TimeUnit.SECONDS);
+ responseFuture.get(5, TimeUnit.SECONDS);
+
+ fail();
+ } catch (ExecutionException e) {
+ assertThat(e).hasCauseInstanceOf(ConnectionClosedException.class);
+ await().atMost(5, TimeUnit.SECONDS)
+ .untilAsserted(() -> assertThat(metricRegistry.getMeters()).containsKey("exception"));
+ }
+ }
+
+ @Test
+ public void usesCustomClientConnectionManager() throws Exception {
+ try(PoolingAsyncClientConnectionManager clientConnectionManager = spy(new PoolingAsyncClientConnectionManager())) {
+ client = InstrumentedHttpAsyncClients.custom(metricRegistry, metricNameStrategy, clientConnectionManager).disableAutomaticRetries().build();
+ client.start();
+
+ final SimpleHttpRequest request = SimpleRequestBuilder
+ .get("http://localhost:" + httpServer.getAddress().getPort() + "/")
+ .build();
+ final String metricName = "some.made.up.metric.name";
+
+ httpServer.createContext("/", exchange -> {
+ exchange.sendResponseHeaders(200, 0L);
+ exchange.setStreams(null, null);
+ exchange.getResponseBody().write("TEST".getBytes(StandardCharsets.US_ASCII));
+ exchange.close();
+ });
+ httpServer.start();
+
+ when(metricNameStrategy.getNameFor(any(), any(HttpRequest.class))).thenReturn(metricName);
+
+ final Future<SimpleHttpResponse> responseFuture = client.execute(request, new FutureCallback<SimpleHttpResponse>() {
+ @Override
+ public void completed(SimpleHttpResponse result) {
+ assertThat(result.getCode()).isEqualTo(200);
+ }
+
+ @Override
+ public void failed(Exception ex) {
+ fail();
+ }
+
+ @Override
+ public void cancelled() {
+ fail();
+ }
+ });
+ responseFuture.get(1L, TimeUnit.SECONDS);
+
+ verify(clientConnectionManager, atLeastOnce()).connect(any(), any(), any(), any(), any(), any());
+ }
+ }
+}
diff --git a/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientConnectionManagerTest.java b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientConnectionManagerTest.java
new file mode 100644
index 0000000..c2d1571
--- /dev/null
+++ b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientConnectionManagerTest.java
@@ -0,0 +1,48 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.ArgumentMatchers.any;
+
+public class InstrumentedHttpClientConnectionManagerTest {
+ private final MetricRegistry metricRegistry = new MetricRegistry();
+
+ @Test
+ public void shouldRemoveGauges() {
+ final InstrumentedHttpClientConnectionManager instrumentedHttpClientConnectionManager = InstrumentedHttpClientConnectionManager.builder(metricRegistry).build();
+ assertThat(metricRegistry.getGauges().entrySet().stream()
+ .map(e -> entry(e.getKey(), e.getValue().getValue())))
+ .containsOnly(entry("org.apache.hc.client5.http.io.HttpClientConnectionManager.available-connections", 0),
+ entry("org.apache.hc.client5.http.io.HttpClientConnectionManager.leased-connections", 0),
+ entry("org.apache.hc.client5.http.io.HttpClientConnectionManager.max-connections", 25),
+ entry("org.apache.hc.client5.http.io.HttpClientConnectionManager.pending-connections", 0));
+
+ instrumentedHttpClientConnectionManager.close();
+ Assert.assertEquals(0, metricRegistry.getGauges().size());
+
+ // should be able to create another one with the same name ("")
+ InstrumentedHttpClientConnectionManager.builder(metricRegistry).build().close();
+ }
+
+ @Test
+ public void configurableViaBuilder() {
+ final MetricRegistry registry = Mockito.mock(MetricRegistry.class);
+
+ InstrumentedHttpClientConnectionManager.builder(registry)
+ .name("some-name")
+ .name("some-other-name")
+ .build()
+ .close();
+
+ ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
+ Mockito.verify(registry, Mockito.atLeast(1)).registerGauge(argumentCaptor.capture(), any());
+ assertTrue(argumentCaptor.getValue().contains("some-other-name"));
+ }
+}
diff --git a/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientsTest.java b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientsTest.java
new file mode 100644
index 0000000..8d11929
--- /dev/null
+++ b/metrics-httpclient5/src/test/java/com/codahale/metrics/httpclient5/InstrumentedHttpClientsTest.java
@@ -0,0 +1,77 @@
+package com.codahale.metrics.httpclient5;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.MetricRegistryListener;
+import com.codahale.metrics.Timer;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.NoHttpResponseException;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.InetSocketAddress;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class InstrumentedHttpClientsTest {
+ private final HttpClientMetricNameStrategy metricNameStrategy =
+ mock(HttpClientMetricNameStrategy.class);
+ private final MetricRegistryListener registryListener =
+ mock(MetricRegistryListener.class);
+ private final MetricRegistry metricRegistry = new MetricRegistry();
+ private final HttpClient client =
+ InstrumentedHttpClients.custom(metricRegistry, metricNameStrategy).disableAutomaticRetries().build();
+
+ @Before
+ public void setUp() {
+ metricRegistry.addListener(registryListener);
+ }
+
+ @Test
+ public void registersExpectedMetricsGivenNameStrategy() throws Exception {
+ final HttpGet get = new HttpGet("http://example.com?q=anything");
+ final String metricName = "some.made.up.metric.name";
+
+ when(metricNameStrategy.getNameFor(any(), any(HttpRequest.class)))
+ .thenReturn(metricName);
+
+ client.execute(get);
+
+ verify(registryListener).onTimerAdded(eq(metricName), any(Timer.class));
+ }
+
+ @Test
+ public void registersExpectedExceptionMetrics() throws Exception {
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(0), 0);
+
+ final HttpGet get = new HttpGet("http://localhost:" + httpServer.getAddress().getPort() + "/");
+ final String requestMetricName = "request";
+ final String exceptionMetricName = "exception";
+
+ httpServer.createContext("/", HttpExchange::close);
+ httpServer.start();
+
+ when(metricNameStrategy.getNameFor(any(), any(HttpRequest.class)))
+ .thenReturn(requestMetricName);
+ when(metricNameStrategy.getNameFor(any(), any(Exception.class)))
+ .thenReturn(exceptionMetricName);
+
+ try {
+ client.execute(get);
+ fail();
+ } catch (NoHttpResponseException expected) {
+ assertThat(metricRegistry.getMeters()).containsKey("exception");
+ } finally {
+ httpServer.stop(0);
+ }
+ }
+}
diff --git a/metrics-jakarta-servlet/pom.xml b/metrics-jakarta-servlet/pom.xml
new file mode 100644
index 0000000..7e3eac3
--- /dev/null
+++ b/metrics-jakarta-servlet/pom.xml
@@ -0,0 +1,59 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jakarta-servlet</artifactId>
+ <name>Metrics Integration for Jakarta Servlets</name>
+ <packaging>bundle</packaging>
+ <description>
+ An instrumented filter for servlet environments.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.servlet</javaModuleName>
+ <servlet.version>5.0.0</servlet.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <version>${servlet.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/AbstractInstrumentedFilter.java b/metrics-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/AbstractInstrumentedFilter.java
new file mode 100644
index 0000000..c895565
--- /dev/null
+++ b/metrics-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/AbstractInstrumentedFilter.java
@@ -0,0 +1,218 @@
+package io.dropwizard.metrics.servlet;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import jakarta.servlet.AsyncEvent;
+import jakarta.servlet.AsyncListener;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+/**
+ * {@link Filter} implementation which captures request information and a breakdown of the response
+ * codes being returned.
+ */
+public abstract class AbstractInstrumentedFilter implements Filter {
+ static final String METRIC_PREFIX = "name-prefix";
+
+ private final String otherMetricName;
+ private final Map<Integer, String> meterNamesByStatusCode;
+ private final String registryAttribute;
+
+ // 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;
+
+
+ /**
+ * Creates a new instance of the filter.
+ *
+ * @param registryAttribute the attribute used to look up the metrics registry in the
+ * servlet context
+ * @param meterNamesByStatusCode A map, keyed by status code, of meter names that we are
+ * interested in.
+ * @param otherMetricName The name used for the catch-all meter.
+ */
+ protected AbstractInstrumentedFilter(String registryAttribute,
+ Map<Integer, String> meterNamesByStatusCode,
+ String otherMetricName) {
+ this.registryAttribute = registryAttribute;
+ this.otherMetricName = otherMetricName;
+ this.meterNamesByStatusCode = meterNamesByStatusCode;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ final MetricRegistry metricsRegistry = getMetricsFactory(filterConfig);
+
+ String metricName = filterConfig.getInitParameter(METRIC_PREFIX);
+ if (metricName == null || metricName.isEmpty()) {
+ metricName = getClass().getName();
+ }
+
+ this.metersByStatusCode = new ConcurrentHashMap<>(meterNamesByStatusCode.size());
+ for (Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) {
+ metersByStatusCode.put(entry.getKey(),
+ metricsRegistry.meter(name(metricName, entry.getValue())));
+ }
+ 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, "requests"));
+
+ }
+
+ private MetricRegistry getMetricsFactory(FilterConfig filterConfig) {
+ final MetricRegistry metricsRegistry;
+
+ final Object o = filterConfig.getServletContext().getAttribute(this.registryAttribute);
+ if (o instanceof MetricRegistry) {
+ metricsRegistry = (MetricRegistry) o;
+ } else {
+ metricsRegistry = new MetricRegistry();
+ }
+ return metricsRegistry;
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest request,
+ ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ final StatusExposingServletResponse wrappedResponse =
+ new StatusExposingServletResponse((HttpServletResponse) response);
+ activeRequests.inc();
+ final Timer.Context context = requestTimer.time();
+ boolean error = false;
+ try {
+ chain.doFilter(request, wrappedResponse);
+ } catch (IOException | RuntimeException | ServletException e) {
+ error = true;
+ throw e;
+ } finally {
+ if (!error && request.isAsyncStarted()) {
+ request.getAsyncContext().addListener(new AsyncResultListener(context));
+ } else {
+ context.stop();
+ activeRequests.dec();
+ if (error) {
+ errorsMeter.mark();
+ } else {
+ markMeterForStatusCode(wrappedResponse.getStatus());
+ }
+ }
+ }
+ }
+
+ private void markMeterForStatusCode(int status) {
+ final Meter metric = metersByStatusCode.get(status);
+ if (metric != null) {
+ metric.mark();
+ } else {
+ otherMeter.mark();
+ }
+ }
+
+ private static class StatusExposingServletResponse extends HttpServletResponseWrapper {
+ // The Servlet spec says: calling setStatus is optional, if no status is set, the default is 200.
+ private int httpStatus = 200;
+
+ public StatusExposingServletResponse(HttpServletResponse response) {
+ super(response);
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ httpStatus = sc;
+ super.sendError(sc);
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ httpStatus = sc;
+ super.sendError(sc, msg);
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ httpStatus = sc;
+ super.setStatus(sc);
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void setStatus(int sc, String sm) {
+ httpStatus = sc;
+ super.setStatus(sc, sm);
+ }
+
+ @Override
+ public int getStatus() {
+ 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-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/InstrumentedFilter.java b/metrics-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/InstrumentedFilter.java
new file mode 100644
index 0000000..17538af
--- /dev/null
+++ b/metrics-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/InstrumentedFilter.java
@@ -0,0 +1,48 @@
+package io.dropwizard.metrics.servlet;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of the {@link AbstractInstrumentedFilter} which provides a default set of response codes
+ * to capture information about. <p>Use it in your servlet.xml like this:<p>
+ * <pre>{@code
+ * <filter>
+ * <filter-name>instrumentedFilter</filter-name>
+ * <filter-class>io.dropwizard.metrics.servlet.InstrumentedFilter</filter-class>
+ * </filter>
+ * <filter-mapping>
+ * <filter-name>instrumentedFilter</filter-name>
+ * <url-pattern>/*</url-pattern>
+ * </filter-mapping>
+ * }</pre>
+ */
+public class InstrumentedFilter extends AbstractInstrumentedFilter {
+ public static final String REGISTRY_ATTRIBUTE = InstrumentedFilter.class.getName() + ".registry";
+
+ private static final String NAME_PREFIX = "responseCodes.";
+ private static final int OK = 200;
+ private static final int CREATED = 201;
+ private static final int NO_CONTENT = 204;
+ private static final int BAD_REQUEST = 400;
+ private static final int NOT_FOUND = 404;
+ private static final int SERVER_ERROR = 500;
+
+ /**
+ * Creates a new instance of the filter.
+ */
+ public InstrumentedFilter() {
+ super(REGISTRY_ATTRIBUTE, createMeterNamesByStatusCode(), NAME_PREFIX + "other");
+ }
+
+ private static Map<Integer, String> createMeterNamesByStatusCode() {
+ final Map<Integer, String> meterNamesByStatusCode = new HashMap<>(6);
+ meterNamesByStatusCode.put(OK, NAME_PREFIX + "ok");
+ meterNamesByStatusCode.put(CREATED, NAME_PREFIX + "created");
+ meterNamesByStatusCode.put(NO_CONTENT, NAME_PREFIX + "noContent");
+ meterNamesByStatusCode.put(BAD_REQUEST, NAME_PREFIX + "badRequest");
+ meterNamesByStatusCode.put(NOT_FOUND, NAME_PREFIX + "notFound");
+ meterNamesByStatusCode.put(SERVER_ERROR, NAME_PREFIX + "serverError");
+ return meterNamesByStatusCode;
+ }
+}
diff --git a/metrics-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/InstrumentedFilterContextListener.java b/metrics-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/InstrumentedFilterContextListener.java
new file mode 100644
index 0000000..04d7b65
--- /dev/null
+++ b/metrics-jakarta-servlet/src/main/java/io/dropwizard/metrics/servlet/InstrumentedFilterContextListener.java
@@ -0,0 +1,26 @@
+package io.dropwizard.metrics.servlet;
+
+import com.codahale.metrics.MetricRegistry;
+import jakarta.servlet.ServletContextEvent;
+import jakarta.servlet.ServletContextListener;
+
+/**
+ * A listener implementation which injects a {@link MetricRegistry} instance into the servlet
+ * context. Implement {@link #getMetricRegistry()} to return the {@link MetricRegistry} for your
+ * application.
+ */
+public abstract class InstrumentedFilterContextListener implements ServletContextListener {
+ /**
+ * @return the {@link MetricRegistry} to inject into the servlet context.
+ */
+ protected abstract MetricRegistry getMetricRegistry();
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ sce.getServletContext().setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE, getMetricRegistry());
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ }
+}
diff --git a/metrics-jakarta-servlet/src/test/java/io/dropwizard/metrics/servlet/InstrumentedFilterContextListenerTest.java b/metrics-jakarta-servlet/src/test/java/io/dropwizard/metrics/servlet/InstrumentedFilterContextListenerTest.java
new file mode 100644
index 0000000..b586a8d
--- /dev/null
+++ b/metrics-jakarta-servlet/src/test/java/io/dropwizard/metrics/servlet/InstrumentedFilterContextListenerTest.java
@@ -0,0 +1,32 @@
+package io.dropwizard.metrics.servlet;
+
+import com.codahale.metrics.MetricRegistry;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletContextEvent;
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class InstrumentedFilterContextListenerTest {
+ private final MetricRegistry registry = mock(MetricRegistry.class);
+ private final InstrumentedFilterContextListener listener = new InstrumentedFilterContextListener() {
+ @Override
+ protected MetricRegistry getMetricRegistry() {
+ return registry;
+ }
+ };
+
+ @Test
+ public void injectsTheMetricRegistryIntoTheServletContext() {
+ final ServletContext context = mock(ServletContext.class);
+
+ final ServletContextEvent event = mock(ServletContextEvent.class);
+ when(event.getServletContext()).thenReturn(context);
+
+ listener.contextInitialized(event);
+
+ verify(context).setAttribute("io.dropwizard.metrics.servlet.InstrumentedFilter.registry", registry);
+ }
+}
diff --git a/metrics-jakarta-servlet6/pom.xml b/metrics-jakarta-servlet6/pom.xml
new file mode 100644
index 0000000..c5d796f
--- /dev/null
+++ b/metrics-jakarta-servlet6/pom.xml
@@ -0,0 +1,59 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jakarta-servlet6</artifactId>
+ <name>Metrics Integration for Jakarta Servlets 6.x</name>
+ <packaging>bundle</packaging>
+ <description>
+ An instrumented filter for servlet 6.x environments.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.servlet</javaModuleName>
+ <servlet6.version>6.0.0</servlet6.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <version>${servlet6.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/AbstractInstrumentedFilter.java b/metrics-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/AbstractInstrumentedFilter.java
new file mode 100644
index 0000000..9134247
--- /dev/null
+++ b/metrics-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/AbstractInstrumentedFilter.java
@@ -0,0 +1,211 @@
+package io.dropwizard.metrics.servlet6;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import jakarta.servlet.AsyncEvent;
+import jakarta.servlet.AsyncListener;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+/**
+ * {@link Filter} implementation which captures request information and a breakdown of the response
+ * codes being returned.
+ */
+public abstract class AbstractInstrumentedFilter implements Filter {
+ static final String METRIC_PREFIX = "name-prefix";
+
+ private final String otherMetricName;
+ private final Map<Integer, String> meterNamesByStatusCode;
+ private final String registryAttribute;
+
+ // 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;
+
+
+ /**
+ * Creates a new instance of the filter.
+ *
+ * @param registryAttribute the attribute used to look up the metrics registry in the
+ * servlet context
+ * @param meterNamesByStatusCode A map, keyed by status code, of meter names that we are
+ * interested in.
+ * @param otherMetricName The name used for the catch-all meter.
+ */
+ protected AbstractInstrumentedFilter(String registryAttribute,
+ Map<Integer, String> meterNamesByStatusCode,
+ String otherMetricName) {
+ this.registryAttribute = registryAttribute;
+ this.otherMetricName = otherMetricName;
+ this.meterNamesByStatusCode = meterNamesByStatusCode;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ final MetricRegistry metricsRegistry = getMetricsFactory(filterConfig);
+
+ String metricName = filterConfig.getInitParameter(METRIC_PREFIX);
+ if (metricName == null || metricName.isEmpty()) {
+ metricName = getClass().getName();
+ }
+
+ this.metersByStatusCode = new ConcurrentHashMap<>(meterNamesByStatusCode.size());
+ for (Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) {
+ metersByStatusCode.put(entry.getKey(),
+ metricsRegistry.meter(name(metricName, entry.getValue())));
+ }
+ 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, "requests"));
+
+ }
+
+ private MetricRegistry getMetricsFactory(FilterConfig filterConfig) {
+ final MetricRegistry metricsRegistry;
+
+ final Object o = filterConfig.getServletContext().getAttribute(this.registryAttribute);
+ if (o instanceof MetricRegistry) {
+ metricsRegistry = (MetricRegistry) o;
+ } else {
+ metricsRegistry = new MetricRegistry();
+ }
+ return metricsRegistry;
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest request,
+ ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ final StatusExposingServletResponse wrappedResponse =
+ new StatusExposingServletResponse((HttpServletResponse) response);
+ activeRequests.inc();
+ final Timer.Context context = requestTimer.time();
+ boolean error = false;
+ try {
+ chain.doFilter(request, wrappedResponse);
+ } catch (IOException | RuntimeException | ServletException e) {
+ error = true;
+ throw e;
+ } finally {
+ if (!error && request.isAsyncStarted()) {
+ request.getAsyncContext().addListener(new AsyncResultListener(context));
+ } else {
+ context.stop();
+ activeRequests.dec();
+ if (error) {
+ errorsMeter.mark();
+ } else {
+ markMeterForStatusCode(wrappedResponse.getStatus());
+ }
+ }
+ }
+ }
+
+ private void markMeterForStatusCode(int status) {
+ final Meter metric = metersByStatusCode.get(status);
+ if (metric != null) {
+ metric.mark();
+ } else {
+ otherMeter.mark();
+ }
+ }
+
+ private static class StatusExposingServletResponse extends HttpServletResponseWrapper {
+ // The Servlet spec says: calling setStatus is optional, if no status is set, the default is 200.
+ private int httpStatus = 200;
+
+ public StatusExposingServletResponse(HttpServletResponse response) {
+ super(response);
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ httpStatus = sc;
+ super.sendError(sc);
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ httpStatus = sc;
+ super.sendError(sc, msg);
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ httpStatus = sc;
+ super.setStatus(sc);
+ }
+
+ @Override
+ public int getStatus() {
+ return httpStatus;
+ }
+ }
+
+ private class AsyncResultListener implements AsyncListener {
+ private final 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-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/InstrumentedFilter.java b/metrics-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/InstrumentedFilter.java
new file mode 100644
index 0000000..e4b37fd
--- /dev/null
+++ b/metrics-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/InstrumentedFilter.java
@@ -0,0 +1,48 @@
+package io.dropwizard.metrics.servlet6;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of the {@link AbstractInstrumentedFilter} which provides a default set of response codes
+ * to capture information about. <p>Use it in your servlet.xml like this:<p>
+ * <pre>{@code
+ * <filter>
+ * <filter-name>instrumentedFilter</filter-name>
+ * <filter-class>io.dropwizard.metrics.servlet.InstrumentedFilter</filter-class>
+ * </filter>
+ * <filter-mapping>
+ * <filter-name>instrumentedFilter</filter-name>
+ * <url-pattern>/*</url-pattern>
+ * </filter-mapping>
+ * }</pre>
+ */
+public class InstrumentedFilter extends AbstractInstrumentedFilter {
+ public static final String REGISTRY_ATTRIBUTE = InstrumentedFilter.class.getName() + ".registry";
+
+ private static final String NAME_PREFIX = "responseCodes.";
+ private static final int OK = 200;
+ private static final int CREATED = 201;
+ private static final int NO_CONTENT = 204;
+ private static final int BAD_REQUEST = 400;
+ private static final int NOT_FOUND = 404;
+ private static final int SERVER_ERROR = 500;
+
+ /**
+ * Creates a new instance of the filter.
+ */
+ public InstrumentedFilter() {
+ super(REGISTRY_ATTRIBUTE, createMeterNamesByStatusCode(), NAME_PREFIX + "other");
+ }
+
+ private static Map<Integer, String> createMeterNamesByStatusCode() {
+ final Map<Integer, String> meterNamesByStatusCode = new HashMap<>(6);
+ meterNamesByStatusCode.put(OK, NAME_PREFIX + "ok");
+ meterNamesByStatusCode.put(CREATED, NAME_PREFIX + "created");
+ meterNamesByStatusCode.put(NO_CONTENT, NAME_PREFIX + "noContent");
+ meterNamesByStatusCode.put(BAD_REQUEST, NAME_PREFIX + "badRequest");
+ meterNamesByStatusCode.put(NOT_FOUND, NAME_PREFIX + "notFound");
+ meterNamesByStatusCode.put(SERVER_ERROR, NAME_PREFIX + "serverError");
+ return meterNamesByStatusCode;
+ }
+}
diff --git a/metrics-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/InstrumentedFilterContextListener.java b/metrics-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/InstrumentedFilterContextListener.java
new file mode 100644
index 0000000..b931584
--- /dev/null
+++ b/metrics-jakarta-servlet6/src/main/java/io/dropwizard/metrics/servlet6/InstrumentedFilterContextListener.java
@@ -0,0 +1,26 @@
+package io.dropwizard.metrics.servlet6;
+
+import com.codahale.metrics.MetricRegistry;
+import jakarta.servlet.ServletContextEvent;
+import jakarta.servlet.ServletContextListener;
+
+/**
+ * A listener implementation which injects a {@link MetricRegistry} instance into the servlet
+ * context. Implement {@link #getMetricRegistry()} to return the {@link MetricRegistry} for your
+ * application.
+ */
+public abstract class InstrumentedFilterContextListener implements ServletContextListener {
+ /**
+ * @return the {@link MetricRegistry} to inject into the servlet context.
+ */
+ protected abstract MetricRegistry getMetricRegistry();
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ sce.getServletContext().setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE, getMetricRegistry());
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ }
+}
diff --git a/metrics-jakarta-servlet6/src/test/java/io/dropwizard/metrics/servlet6/InstrumentedFilterContextListenerTest.java b/metrics-jakarta-servlet6/src/test/java/io/dropwizard/metrics/servlet6/InstrumentedFilterContextListenerTest.java
new file mode 100644
index 0000000..74062ef
--- /dev/null
+++ b/metrics-jakarta-servlet6/src/test/java/io/dropwizard/metrics/servlet6/InstrumentedFilterContextListenerTest.java
@@ -0,0 +1,32 @@
+package io.dropwizard.metrics.servlet6;
+
+import com.codahale.metrics.MetricRegistry;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletContextEvent;
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class InstrumentedFilterContextListenerTest {
+ private final MetricRegistry registry = mock(MetricRegistry.class);
+ private final InstrumentedFilterContextListener listener = new InstrumentedFilterContextListener() {
+ @Override
+ protected MetricRegistry getMetricRegistry() {
+ return registry;
+ }
+ };
+
+ @Test
+ public void injectsTheMetricRegistryIntoTheServletContext() {
+ final ServletContext context = mock(ServletContext.class);
+
+ final ServletContextEvent event = mock(ServletContextEvent.class);
+ when(event.getServletContext()).thenReturn(context);
+
+ listener.contextInitialized(event);
+
+ verify(context).setAttribute("io.dropwizard.metrics.servlet6.InstrumentedFilter.registry", registry);
+ }
+}
diff --git a/metrics-jakarta-servlets/pom.xml b/metrics-jakarta-servlets/pom.xml
new file mode 100644
index 0000000..69928ab
--- /dev/null
+++ b/metrics-jakarta-servlets/pom.xml
@@ -0,0 +1,169 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jakarta-servlets</artifactId>
+ <name>Metrics Utility Jakarta Servlets</name>
+ <packaging>bundle</packaging>
+ <description>
+ A set of utility servlets for Metrics, allowing you to expose valuable information about
+ your production environment.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.servlets</javaModuleName>
+ <papertrail.profiler.version>1.1.1</papertrail.profiler.version>
+ <servlet.version>6.0.0</servlet.version>
+ <jackson.version>2.12.7.1</jackson.version>
+ <slf4j.version>2.0.11</slf4j.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-bom</artifactId>
+ <version>${jetty11.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-healthchecks</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-json</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jvm</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.helger</groupId>
+ <artifactId>profiler</artifactId>
+ <version>${papertrail.profiler.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <version>${servlet.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jetty11</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/AdminServlet.java b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/AdminServlet.java
new file mode 100755
index 0000000..447d1c1
--- /dev/null
+++ b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/AdminServlet.java
@@ -0,0 +1,190 @@
+package io.dropwizard.metrics.servlets;
+
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+
+public class AdminServlet extends HttpServlet {
+ public static final String DEFAULT_HEALTHCHECK_URI = "/healthcheck";
+ 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_ENABLED_PARAM_KEY = "metrics-enabled";
+ public static final String METRICS_URI_PARAM_KEY = "metrics-uri";
+ public static final String PING_ENABLED_PARAM_KEY = "ping-enabled";
+ public static final String PING_URI_PARAM_KEY = "ping-uri";
+ public static final String THREADS_ENABLED_PARAM_KEY = "threads-enabled";
+ public static final String THREADS_URI_PARAM_KEY = "threads-uri";
+ public static final String HEALTHCHECK_ENABLED_PARAM_KEY = "healthcheck-enabled";
+ 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_ENABLED_PARAM_KEY = "cpu-profile-enabled";
+ public static final String CPU_PROFILE_URI_PARAM_KEY = "cpu-profile-uri";
+
+ private static final String BASE_TEMPLATE =
+ "<!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{10}</title>%n" +
+ "</head>%n" +
+ "<body>%n" +
+ " <h1>Operational Menu{10}</h1>%n" +
+ " <ul>%n" +
+ "%s" +
+ " </ul>%n" +
+ "</body>%n" +
+ "</html>";
+ private static final String METRICS_LINK = " <li><a href=\"{0}{1}?pretty=true\">Metrics</a></li>%n";
+ private static final String PING_LINK = " <li><a href=\"{2}{3}\">Ping</a></li>%n" ;
+ private static final String THREADS_LINK = " <li><a href=\"{4}{5}\">Threads</a></li>%n" ;
+ private static final String HEALTHCHECK_LINK = " <li><a href=\"{6}{7}?pretty=true\">Healthcheck</a></li>%n" ;
+ private static final String CPU_PROFILE_LINK = " <li><a href=\"{8}{9}\">CPU Profile</a></li>%n" +
+ " <li><a href=\"{8}{9}?state=blocked\">CPU Contention</a></li>%n";
+
+
+ private static final String CONTENT_TYPE = "text/html";
+ private static final long serialVersionUID = -2850794040708785318L;
+
+ private transient HealthCheckServlet healthCheckServlet;
+ private transient MetricsServlet metricsServlet;
+ private transient PingServlet pingServlet;
+ private transient ThreadDumpServlet threadDumpServlet;
+ private transient CpuProfileServlet cpuProfileServlet;
+ private transient boolean metricsEnabled;
+ private transient String metricsUri;
+ private transient boolean pingEnabled;
+ private transient String pingUri;
+ private transient boolean threadsEnabled;
+ private transient String threadsUri;
+ private transient boolean healthcheckEnabled;
+ private transient String healthcheckUri;
+ private transient boolean cpuProfileEnabled;
+ private transient String cpuProfileUri;
+ private transient String serviceName;
+ private transient String pageContentTemplate;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+
+ final ServletContext context = config.getServletContext();
+ final StringBuilder servletLinks = new StringBuilder();
+
+ this.metricsEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(METRICS_ENABLED_PARAM_KEY), "true"));
+ if (this.metricsEnabled) {
+ servletLinks.append(METRICS_LINK);
+ }
+ this.metricsServlet = new MetricsServlet();
+ metricsServlet.init(config);
+
+ this.pingEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(PING_ENABLED_PARAM_KEY), "true"));
+ if (this.pingEnabled) {
+ servletLinks.append(PING_LINK);
+ }
+ this.pingServlet = new PingServlet();
+ pingServlet.init(config);
+
+ this.threadsEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(THREADS_ENABLED_PARAM_KEY), "true"));
+ if (this.threadsEnabled) {
+ servletLinks.append(THREADS_LINK);
+ }
+ this.threadDumpServlet = new ThreadDumpServlet();
+ threadDumpServlet.init(config);
+
+ this.healthcheckEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(HEALTHCHECK_ENABLED_PARAM_KEY), "true"));
+ if (this.healthcheckEnabled) {
+ servletLinks.append(HEALTHCHECK_LINK);
+ }
+ this.healthCheckServlet = new HealthCheckServlet();
+ healthCheckServlet.init(config);
+
+ this.cpuProfileEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(CPU_PROFILE_ENABLED_PARAM_KEY), "true"));
+ if (this.cpuProfileEnabled) {
+ servletLinks.append(CPU_PROFILE_LINK);
+ }
+ this.cpuProfileServlet = new CpuProfileServlet();
+ cpuProfileServlet.init(config);
+
+ pageContentTemplate = String.format(BASE_TEMPLATE, String.format(servletLinks.toString()));
+
+ this.metricsUri = getParam(context.getInitParameter(METRICS_URI_PARAM_KEY), DEFAULT_METRICS_URI);
+ this.pingUri = getParam(context.getInitParameter(PING_URI_PARAM_KEY), DEFAULT_PING_URI);
+ this.threadsUri = getParam(context.getInitParameter(THREADS_URI_PARAM_KEY), DEFAULT_THREADS_URI);
+ this.healthcheckUri = getParam(context.getInitParameter(HEALTHCHECK_URI_PARAM_KEY), DEFAULT_HEALTHCHECK_URI);
+ this.cpuProfileUri = getParam(context.getInitParameter(CPU_PROFILE_URI_PARAM_KEY), DEFAULT_CPU_PROFILE_URI);
+ this.serviceName = getParam(context.getInitParameter(SERVICE_NAME_PARAM_KEY), null);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ final String path = req.getContextPath() + req.getServletPath();
+
+ resp.setStatus(HttpServletResponse.SC_OK);
+ resp.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
+ resp.setContentType(CONTENT_TYPE);
+ try (PrintWriter writer = resp.getWriter()) {
+ writer.println(MessageFormat.format(pageContentTemplate, path, metricsUri, path, pingUri, path,
+ threadsUri, path, healthcheckUri, path, cpuProfileUri,
+ serviceName == null ? "" : " (" + serviceName + ")"));
+ }
+ }
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ final String uri = req.getPathInfo();
+ if (uri == null || uri.equals("/")) {
+ super.service(req, resp);
+ } else if (uri.equals(healthcheckUri)) {
+ if (healthcheckEnabled) {
+ healthCheckServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } else if (uri.startsWith(metricsUri)) {
+ if (metricsEnabled) {
+ metricsServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } else if (uri.equals(pingUri)) {
+ if (pingEnabled) {
+ pingServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } else if (uri.equals(threadsUri)) {
+ if (threadsEnabled) {
+ threadDumpServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } else if (uri.equals(cpuProfileUri)) {
+ if (cpuProfileEnabled) {
+ cpuProfileServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
+
+ private static String getParam(String initParam, String defaultValue) {
+ return initParam == null ? defaultValue : initParam;
+ }
+}
diff --git a/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/CpuProfileServlet.java b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/CpuProfileServlet.java
new file mode 100644
index 0000000..3e05af6
--- /dev/null
+++ b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/CpuProfileServlet.java
@@ -0,0 +1,79 @@
+package io.dropwizard.metrics.servlets;
+
+import com.papertrail.profiler.CpuProfile;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.time.Duration;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 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"));
+ frequency = Math.min(Math.max(frequency, 1), 1000);
+ } 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);
+ try (OutputStream output = resp.getOutputStream()) {
+ doProfile(output, duration, frequency, state);
+ }
+ }
+
+ protected void doProfile(OutputStream out, int duration, int frequency, Thread.State state) throws IOException {
+ if (lock.tryLock()) {
+ try {
+ CpuProfile profile = CpuProfile.record(Duration.ofSeconds(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-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/HealthCheckServlet.java b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/HealthCheckServlet.java
new file mode 100644
index 0000000..2af0d91
--- /dev/null
+++ b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/HealthCheckServlet.java
@@ -0,0 +1,195 @@
+package io.dropwizard.metrics.servlets;
+
+import com.codahale.metrics.health.HealthCheck;
+import com.codahale.metrics.health.HealthCheckFilter;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.codahale.metrics.json.HealthCheckModule;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletContextEvent;
+import jakarta.servlet.ServletContextListener;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.concurrent.ExecutorService;
+
+public class HealthCheckServlet extends HttpServlet {
+ public static abstract class ContextListener implements ServletContextListener {
+ /**
+ * @return the {@link HealthCheckRegistry} to inject into the servlet context.
+ */
+ protected abstract HealthCheckRegistry getHealthCheckRegistry();
+
+ /**
+ * @return the {@link ExecutorService} to inject into the servlet context, or {@code null}
+ * if the health checks should be run in the servlet worker thread.
+ */
+ protected ExecutorService getExecutorService() {
+ // don't use a thread pool by default
+ return null;
+ }
+
+ /**
+ * @return the {@link HealthCheckFilter} that shall be used to filter health checks,
+ * or {@link HealthCheckFilter#ALL} if the default should be used.
+ */
+ protected HealthCheckFilter getHealthCheckFilter() {
+ return HealthCheckFilter.ALL;
+ }
+
+ /**
+ * @return the {@link ObjectMapper} that shall be used to render health checks,
+ * or {@code null} if the default object mapper should be used.
+ */
+ protected ObjectMapper getObjectMapper() {
+ // don't use an object mapper by default
+ return null;
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent event) {
+ final ServletContext context = event.getServletContext();
+ context.setAttribute(HEALTH_CHECK_REGISTRY, getHealthCheckRegistry());
+ context.setAttribute(HEALTH_CHECK_EXECUTOR, getExecutorService());
+ context.setAttribute(HEALTH_CHECK_MAPPER, getObjectMapper());
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent event) {
+ // no-op
+ }
+ }
+
+ public static final String HEALTH_CHECK_REGISTRY = HealthCheckServlet.class.getCanonicalName() + ".registry";
+ public static final String HEALTH_CHECK_EXECUTOR = HealthCheckServlet.class.getCanonicalName() + ".executor";
+ public static final String HEALTH_CHECK_FILTER = HealthCheckServlet.class.getCanonicalName() + ".healthCheckFilter";
+ public static final String HEALTH_CHECK_MAPPER = HealthCheckServlet.class.getCanonicalName() + ".mapper";
+ public static final String HEALTH_CHECK_HTTP_STATUS_INDICATOR = HealthCheckServlet.class.getCanonicalName() + ".httpStatusIndicator";
+
+ private static final long serialVersionUID = -8432996484889177321L;
+ private static final String CONTENT_TYPE = "application/json";
+ private static final String HTTP_STATUS_INDICATOR_PARAM = "httpStatusIndicator";
+
+ private transient HealthCheckRegistry registry;
+ private transient ExecutorService executorService;
+ private transient HealthCheckFilter filter;
+ private transient ObjectMapper mapper;
+ private transient boolean httpStatusIndicator;
+
+ public HealthCheckServlet() {
+ }
+
+ public HealthCheckServlet(HealthCheckRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+
+ final ServletContext context = config.getServletContext();
+ if (null == registry) {
+ final Object registryAttr = context.getAttribute(HEALTH_CHECK_REGISTRY);
+ if (registryAttr instanceof HealthCheckRegistry) {
+ this.registry = (HealthCheckRegistry) registryAttr;
+ } else {
+ throw new ServletException("Couldn't find a HealthCheckRegistry instance.");
+ }
+ }
+
+ final Object executorAttr = context.getAttribute(HEALTH_CHECK_EXECUTOR);
+ if (executorAttr instanceof ExecutorService) {
+ this.executorService = (ExecutorService) executorAttr;
+ }
+
+ final Object filterAttr = context.getAttribute(HEALTH_CHECK_FILTER);
+ if (filterAttr instanceof HealthCheckFilter) {
+ filter = (HealthCheckFilter) filterAttr;
+ }
+ if (filter == null) {
+ filter = HealthCheckFilter.ALL;
+ }
+
+ final Object mapperAttr = context.getAttribute(HEALTH_CHECK_MAPPER);
+ if (mapperAttr instanceof ObjectMapper) {
+ this.mapper = (ObjectMapper) mapperAttr;
+ } else {
+ this.mapper = new ObjectMapper();
+ }
+ this.mapper.registerModule(new HealthCheckModule());
+
+ final Object httpStatusIndicatorAttr = context.getAttribute(HEALTH_CHECK_HTTP_STATUS_INDICATOR);
+ if (httpStatusIndicatorAttr instanceof Boolean) {
+ this.httpStatusIndicator = (Boolean) httpStatusIndicatorAttr;
+ } else {
+ this.httpStatusIndicator = true;
+ }
+ }
+
+ @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();
+ resp.setContentType(CONTENT_TYPE);
+ resp.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
+ if (results.isEmpty()) {
+ resp.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
+ } else {
+ final String reqParameter = req.getParameter(HTTP_STATUS_INDICATOR_PARAM);
+ final boolean httpStatusIndicatorParam = Boolean.parseBoolean(reqParameter);
+ final boolean useHttpStatusForHealthCheck = reqParameter == null ? httpStatusIndicator : httpStatusIndicatorParam;
+ if (!useHttpStatusForHealthCheck || isAllHealthy(results)) {
+ resp.setStatus(HttpServletResponse.SC_OK);
+ } else {
+ resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ try (OutputStream output = resp.getOutputStream()) {
+ getWriter(req).writeValue(output, results);
+ }
+ }
+
+ private ObjectWriter getWriter(HttpServletRequest request) {
+ final boolean prettyPrint = Boolean.parseBoolean(request.getParameter("pretty"));
+ if (prettyPrint) {
+ return mapper.writerWithDefaultPrettyPrinter();
+ }
+ return mapper.writer();
+ }
+
+ private SortedMap<String, HealthCheck.Result> runHealthChecks() {
+ if (executorService == null) {
+ return registry.runHealthChecks(filter);
+ }
+ return registry.runHealthChecks(executorService, filter);
+ }
+
+ private static boolean isAllHealthy(Map<String, HealthCheck.Result> results) {
+ for (HealthCheck.Result result : results.values()) {
+ if (!result.isHealthy()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // visible for testing
+ ObjectMapper getMapper() {
+ return mapper;
+ }
+}
diff --git a/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/MetricsServlet.java b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/MetricsServlet.java
new file mode 100644
index 0000000..a248dd8
--- /dev/null
+++ b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/MetricsServlet.java
@@ -0,0 +1,198 @@
+package io.dropwizard.metrics.servlets;
+
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.json.MetricsModule;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.util.JSONPObject;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletContextEvent;
+import jakarta.servlet.ServletContextListener;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A servlet which returns the metrics in a given registry as an {@code application/json} response.
+ */
+public class MetricsServlet extends HttpServlet {
+ /**
+ * An abstract {@link ServletContextListener} which allows you to programmatically inject the
+ * {@link MetricRegistry}, rate and duration units, and allowed origin for
+ * {@link MetricsServlet}.
+ */
+ public static abstract class ContextListener implements ServletContextListener {
+ /**
+ * @return the {@link MetricRegistry} to inject into the servlet context.
+ */
+ protected abstract MetricRegistry getMetricRegistry();
+
+ /**
+ * @return the {@link TimeUnit} to which rates should be converted, or {@code null} if the
+ * default should be used.
+ */
+ protected TimeUnit getRateUnit() {
+ // use the default
+ return null;
+ }
+
+ /**
+ * @return the {@link TimeUnit} to which durations should be converted, or {@code null} if
+ * the default should be used.
+ */
+ protected TimeUnit getDurationUnit() {
+ // use the default
+ return null;
+ }
+
+ /**
+ * @return the {@code Access-Control-Allow-Origin} header value, if any.
+ */
+ protected String getAllowedOrigin() {
+ // use the default
+ return null;
+ }
+
+ /**
+ * Returns the name of the parameter used to specify the jsonp callback, if any.
+ */
+ protected String getJsonpCallbackParameter() {
+ return null;
+ }
+
+ /**
+ * Returns the {@link MetricFilter} that shall be used to filter metrics, or {@link MetricFilter#ALL} if
+ * the default should be used.
+ */
+ protected MetricFilter getMetricFilter() {
+ // use the default
+ return MetricFilter.ALL;
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent event) {
+ final ServletContext context = event.getServletContext();
+ context.setAttribute(METRICS_REGISTRY, getMetricRegistry());
+ context.setAttribute(METRIC_FILTER, getMetricFilter());
+ if (getDurationUnit() != null) {
+ context.setInitParameter(MetricsServlet.DURATION_UNIT, getDurationUnit().toString());
+ }
+ if (getRateUnit() != null) {
+ context.setInitParameter(MetricsServlet.RATE_UNIT, getRateUnit().toString());
+ }
+ if (getAllowedOrigin() != null) {
+ context.setInitParameter(MetricsServlet.ALLOWED_ORIGIN, getAllowedOrigin());
+ }
+ if (getJsonpCallbackParameter() != null) {
+ context.setAttribute(CALLBACK_PARAM, getJsonpCallbackParameter());
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent event) {
+ // no-op
+ }
+ }
+
+ public static final String RATE_UNIT = MetricsServlet.class.getCanonicalName() + ".rateUnit";
+ public static final String DURATION_UNIT = MetricsServlet.class.getCanonicalName() + ".durationUnit";
+ public static final String SHOW_SAMPLES = MetricsServlet.class.getCanonicalName() + ".showSamples";
+ public static final String METRICS_REGISTRY = MetricsServlet.class.getCanonicalName() + ".registry";
+ public static final String ALLOWED_ORIGIN = MetricsServlet.class.getCanonicalName() + ".allowedOrigin";
+ public static final String METRIC_FILTER = MetricsServlet.class.getCanonicalName() + ".metricFilter";
+ public static final String CALLBACK_PARAM = MetricsServlet.class.getCanonicalName() + ".jsonpCallback";
+
+ private static final long serialVersionUID = 1049773947734939602L;
+ private static final String CONTENT_TYPE = "application/json";
+
+ protected String allowedOrigin;
+ protected String jsonpParamName;
+ protected transient MetricRegistry registry;
+ protected transient ObjectMapper mapper;
+
+ public MetricsServlet() {
+ }
+
+ public MetricsServlet(MetricRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+
+ final ServletContext context = config.getServletContext();
+ if (null == registry) {
+ final Object registryAttr = context.getAttribute(METRICS_REGISTRY);
+ if (registryAttr instanceof MetricRegistry) {
+ this.registry = (MetricRegistry) registryAttr;
+ } else {
+ throw new ServletException("Couldn't find a MetricRegistry instance.");
+ }
+ }
+ this.allowedOrigin = context.getInitParameter(ALLOWED_ORIGIN);
+ this.jsonpParamName = context.getInitParameter(CALLBACK_PARAM);
+
+ setupMetricsModule(context);
+ }
+
+ protected void setupMetricsModule(ServletContext context) {
+ final TimeUnit rateUnit = parseTimeUnit(context.getInitParameter(RATE_UNIT),
+ TimeUnit.SECONDS);
+ final TimeUnit durationUnit = parseTimeUnit(context.getInitParameter(DURATION_UNIT),
+ TimeUnit.SECONDS);
+ final boolean showSamples = Boolean.parseBoolean(context.getInitParameter(SHOW_SAMPLES));
+ MetricFilter filter = (MetricFilter) context.getAttribute(METRIC_FILTER);
+ if (filter == null) {
+ filter = MetricFilter.ALL;
+ }
+
+ this.mapper = new ObjectMapper().registerModule(new MetricsModule(rateUnit,
+ durationUnit,
+ showSamples,
+ filter));
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req,
+ HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType(CONTENT_TYPE);
+ if (allowedOrigin != null) {
+ resp.setHeader("Access-Control-Allow-Origin", allowedOrigin);
+ }
+ resp.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
+ resp.setStatus(HttpServletResponse.SC_OK);
+
+ try (OutputStream output = resp.getOutputStream()) {
+ if (jsonpParamName != null && req.getParameter(jsonpParamName) != null) {
+ getWriter(req).writeValue(output, new JSONPObject(req.getParameter(jsonpParamName), registry));
+ } else {
+ getWriter(req).writeValue(output, registry);
+ }
+ }
+ }
+
+ protected ObjectWriter getWriter(HttpServletRequest request) {
+ final boolean prettyPrint = Boolean.parseBoolean(request.getParameter("pretty"));
+ if (prettyPrint) {
+ return mapper.writerWithDefaultPrettyPrinter();
+ }
+ return mapper.writer();
+ }
+
+ protected TimeUnit parseTimeUnit(String value, TimeUnit defaultValue) {
+ try {
+ return TimeUnit.valueOf(String.valueOf(value).toUpperCase(Locale.US));
+ } catch (IllegalArgumentException e) {
+ return defaultValue;
+ }
+ }
+}
diff --git a/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/PingServlet.java b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/PingServlet.java
new file mode 100644
index 0000000..74bacec
--- /dev/null
+++ b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/PingServlet.java
@@ -0,0 +1,31 @@
+package io.dropwizard.metrics.servlets;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * An HTTP servlets which outputs a {@code text/plain} {@code "pong"} response.
+ */
+public class PingServlet extends HttpServlet {
+ private static final long serialVersionUID = 3772654177231086757L;
+ private static final String CONTENT_TYPE = "text/plain";
+ private static final String CONTENT = "pong";
+ private static final String CACHE_CONTROL = "Cache-Control";
+ private static final String NO_CACHE = "must-revalidate,no-cache,no-store";
+
+ @Override
+ protected void doGet(HttpServletRequest req,
+ HttpServletResponse resp) throws ServletException, IOException {
+ resp.setStatus(HttpServletResponse.SC_OK);
+ resp.setHeader(CACHE_CONTROL, NO_CACHE);
+ resp.setContentType(CONTENT_TYPE);
+ try (PrintWriter writer = resp.getWriter()) {
+ writer.println(CONTENT);
+ }
+ }
+}
diff --git a/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/ThreadDumpServlet.java b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/ThreadDumpServlet.java
new file mode 100644
index 0000000..ee1fea3
--- /dev/null
+++ b/metrics-jakarta-servlets/src/main/java/io/dropwizard/metrics/servlets/ThreadDumpServlet.java
@@ -0,0 +1,55 @@
+package io.dropwizard.metrics.servlets;
+
+import com.codahale.metrics.jvm.ThreadDump;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.management.ManagementFactory;
+
+/**
+ * An HTTP servlets which outputs a {@code text/plain} dump of all threads in
+ * the VM. Only responds to {@code GET} requests.
+ */
+public class ThreadDumpServlet extends HttpServlet {
+
+ private static final long serialVersionUID = -2690343532336103046L;
+ private static final String CONTENT_TYPE = "text/plain";
+
+ private transient ThreadDump threadDump;
+
+ @Override
+ public void init() throws ServletException {
+ try {
+ // Some PaaS like Google App Engine blacklist java.lang.managament
+ this.threadDump = new ThreadDump(ManagementFactory.getThreadMXBean());
+ } catch (NoClassDefFoundError ncdfe) {
+ this.threadDump = null; // we won't be able to provide thread dump
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req,
+ HttpServletResponse resp) throws ServletException, IOException {
+ final boolean includeMonitors = getParam(req.getParameter("monitors"), true);
+ final boolean includeSynchronizers = getParam(req.getParameter("synchronizers"), true);
+
+ resp.setStatus(HttpServletResponse.SC_OK);
+ resp.setContentType(CONTENT_TYPE);
+ resp.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
+ if (threadDump == null) {
+ resp.getWriter().println("Sorry your runtime environment does not allow to dump threads.");
+ return;
+ }
+ try (OutputStream output = resp.getOutputStream()) {
+ threadDump.dump(includeMonitors, includeSynchronizers, output);
+ }
+ }
+
+ private static Boolean getParam(String initParam, boolean defaultValue) {
+ return initParam == null ? defaultValue : Boolean.parseBoolean(initParam);
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AbstractServletTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AbstractServletTest.java
new file mode 100644
index 0000000..3afb8bd
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AbstractServletTest.java
@@ -0,0 +1,29 @@
+package io.dropwizard.metrics.servlets;
+
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.After;
+import org.junit.Before;
+
+public abstract class AbstractServletTest {
+ private final ServletTester tester = new ServletTester();
+ protected final HttpTester.Request request = HttpTester.newRequest();
+ protected HttpTester.Response response;
+
+ @Before
+ public void setUpTester() throws Exception {
+ setUp(tester);
+ tester.start();
+ }
+
+ protected abstract void setUp(ServletTester tester);
+
+ @After
+ public void tearDownTester() throws Exception {
+ tester.stop();
+ }
+
+ protected void processRequest() throws Exception {
+ this.response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AdminServletTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AdminServletTest.java
new file mode 100755
index 0000000..102e0a8
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AdminServletTest.java
@@ -0,0 +1,62 @@
+package io.dropwizard.metrics.servlets;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AdminServletTest extends AbstractServletTest {
+ private final MetricRegistry registry = new MetricRegistry();
+ private final HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
+
+ @Override
+ protected void setUp(ServletTester tester) {
+ tester.setContextPath("/context");
+
+ tester.setAttribute("io.dropwizard.metrics.servlets.MetricsServlet.registry", registry);
+ tester.setAttribute("io.dropwizard.metrics.servlets.HealthCheckServlet.registry", healthCheckRegistry);
+ tester.addServlet(AdminServlet.class, "/admin");
+ }
+
+ @Before
+ public void setUp() {
+ request.setMethod("GET");
+ request.setURI("/context/admin");
+ request.setVersion("HTTP/1.0");
+ }
+
+ @Test
+ public void returnsA200() throws Exception {
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.getContent())
+ .isEqualTo(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</title>%n" +
+ "</head>%n" +
+ "<body>%n" +
+ " <h1>Operational Menu</h1>%n" +
+ " <ul>%n" +
+ " <li><a href=\"/context/admin/metrics?pretty=true\">Metrics</a></li>%n" +
+ " <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"
+ ));
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("text/html;charset=UTF-8");
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AdminServletUriTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AdminServletUriTest.java
new file mode 100755
index 0000000..d3d8df5
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/AdminServletUriTest.java
@@ -0,0 +1,67 @@
+package io.dropwizard.metrics.servlets;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AdminServletUriTest extends AbstractServletTest {
+ private final MetricRegistry registry = new MetricRegistry();
+ private final HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
+
+ @Override
+ protected void setUp(ServletTester tester) {
+ tester.setContextPath("/context");
+
+ tester.setAttribute("io.dropwizard.metrics.servlets.MetricsServlet.registry", registry);
+ tester.setAttribute("io.dropwizard.metrics.servlets.HealthCheckServlet.registry", healthCheckRegistry);
+ tester.setInitParameter("metrics-uri", "/metrics-test");
+ tester.setInitParameter("ping-uri", "/ping-test");
+ tester.setInitParameter("threads-uri", "/threads-test");
+ tester.setInitParameter("healthcheck-uri", "/healthcheck-test");
+ tester.setInitParameter("cpu-profile-uri", "/pprof-test");
+ tester.addServlet(AdminServlet.class, "/admin");
+ }
+
+ @Before
+ public void setUp() {
+ request.setMethod("GET");
+ request.setURI("/context/admin");
+ request.setVersion("HTTP/1.0");
+ }
+
+ @Test
+ public void returnsA200() throws Exception {
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.getContent())
+ .isEqualTo(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</title>%n" +
+ "</head>%n" +
+ "<body>%n" +
+ " <h1>Operational Menu</h1>%n" +
+ " <ul>%n" +
+ " <li><a href=\"/context/admin/metrics-test?pretty=true\">Metrics</a></li>%n" +
+ " <li><a href=\"/context/admin/ping-test\">Ping</a></li>%n" +
+ " <li><a href=\"/context/admin/threads-test\">Threads</a></li>%n" +
+ " <li><a href=\"/context/admin/healthcheck-test?pretty=true\">Healthcheck</a></li>%n" +
+ " <li><a href=\"/context/admin/pprof-test\">CPU Profile</a></li>%n" +
+ " <li><a href=\"/context/admin/pprof-test?state=blocked\">CPU Contention</a></li>%n" +
+ " </ul>%n" +
+ "</body>%n" +
+ "</html>%n"
+ ));
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("text/html;charset=UTF-8");
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/CpuProfileServletTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/CpuProfileServletTest.java
new file mode 100644
index 0000000..e724acf
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/CpuProfileServletTest.java
@@ -0,0 +1,44 @@
+package io.dropwizard.metrics.servlets;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+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() {
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ }
+
+ @Test
+ public void returnsPprofRaw() {
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("pprof/raw");
+ }
+
+ @Test
+ public void returnsUncacheable() {
+ assertThat(response.get(HttpHeader.CACHE_CONTROL))
+ .isEqualTo("must-revalidate,no-cache,no-store");
+
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/HealthCheckServletTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/HealthCheckServletTest.java
new file mode 100644
index 0000000..ec5b080
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/HealthCheckServletTest.java
@@ -0,0 +1,255 @@
+package io.dropwizard.metrics.servlets;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.health.HealthCheck;
+import com.codahale.metrics.health.HealthCheckFilter;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class HealthCheckServletTest extends AbstractServletTest {
+
+ private static final ZonedDateTime FIXED_TIME = ZonedDateTime.now();
+
+ private static final DateTimeFormatter DATE_TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+
+ private static final String EXPECTED_TIMESTAMP = DATE_TIME_FORMATTER.format(FIXED_TIME);
+
+ private static final Clock FIXED_CLOCK = new Clock() {
+ @Override
+ public long getTick() {
+ return 0L;
+ }
+
+ @Override
+ public long getTime() {
+ return FIXED_TIME.toInstant().toEpochMilli();
+ }
+ };
+
+ private final HealthCheckRegistry registry = new HealthCheckRegistry();
+ private final ExecutorService threadPool = Executors.newCachedThreadPool();
+
+ @Override
+ protected void setUp(ServletTester tester) {
+ tester.addServlet(io.dropwizard.metrics.servlets.HealthCheckServlet.class, "/healthchecks");
+ tester.setAttribute("io.dropwizard.metrics.servlets.HealthCheckServlet.registry", registry);
+ tester.setAttribute("io.dropwizard.metrics.servlets.HealthCheckServlet.executor", threadPool);
+ tester.setAttribute("io.dropwizard.metrics.servlets.HealthCheckServlet.healthCheckFilter",
+ (HealthCheckFilter) (name, healthCheck) -> !"filtered".equals(name));
+ }
+
+ @Before
+ public void setUp() {
+ request.setMethod("GET");
+ request.setURI("/healthchecks");
+ request.setVersion("HTTP/1.0");
+ }
+
+ @After
+ public void tearDown() {
+ threadPool.shutdown();
+ }
+
+ @Test
+ public void returns501IfNoHealthChecksAreRegistered() throws Exception {
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(501);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent()).isEqualTo("{}");
+ }
+
+ @Test
+ public void returnsA200IfAllHealthChecksAreHealthy() throws Exception {
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("whee")));
+
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent())
+ .isEqualTo("{\"fun\":{\"healthy\":true,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" +
+ EXPECTED_TIMESTAMP +
+ "\"}}");
+ }
+
+ @Test
+ public void returnsASubsetOfHealthChecksIfFiltered() throws Exception {
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("whee")));
+ registry.register("filtered", new TestHealthCheck(() -> unhealthyResultWithMessage("whee")));
+
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent())
+ .isEqualTo("{\"fun\":{\"healthy\":true,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" +
+ EXPECTED_TIMESTAMP +
+ "\"}}");
+ }
+
+ @Test
+ public void returnsA500IfAnyHealthChecksAreUnhealthy() throws Exception {
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("whee")));
+ registry.register("notFun", new TestHealthCheck(() -> unhealthyResultWithMessage("whee")));
+
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(500);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent()).contains(
+ "{\"fun\":{\"healthy\":true,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" + EXPECTED_TIMESTAMP + "\"}",
+ ",\"notFun\":{\"healthy\":false,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" + EXPECTED_TIMESTAMP + "\"}}");
+ }
+
+ @Test
+ public void returnsA200IfAnyHealthChecksAreUnhealthyAndHttpStatusIndicatorIsDisabled() throws Exception {
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("whee")));
+ registry.register("notFun", new TestHealthCheck(() -> unhealthyResultWithMessage("whee")));
+ request.setURI("/healthchecks?httpStatusIndicator=false");
+
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent()).contains(
+ "{\"fun\":{\"healthy\":true,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" + EXPECTED_TIMESTAMP + "\"}",
+ ",\"notFun\":{\"healthy\":false,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" + EXPECTED_TIMESTAMP + "\"}}");
+ }
+
+ @Test
+ public void optionallyPrettyPrintsTheJson() throws Exception {
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("foo bar 123")));
+
+ request.setURI("/healthchecks?pretty=true");
+
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent())
+ .isEqualTo(String.format("{%n" +
+ " \"fun\" : {%n" +
+ " \"healthy\" : true,%n" +
+ " \"message\" : \"foo bar 123\",%n" +
+ " \"duration\" : 0,%n" +
+ " \"timestamp\" : \"" + EXPECTED_TIMESTAMP + "\"" +
+ "%n }%n}"));
+ }
+
+ private static HealthCheck.Result healthyResultWithMessage(String message) {
+ return HealthCheck.Result.builder()
+ .healthy()
+ .withMessage(message)
+ .usingClock(FIXED_CLOCK)
+ .build();
+ }
+
+ private static HealthCheck.Result unhealthyResultWithMessage(String message) {
+ return HealthCheck.Result.builder()
+ .unhealthy()
+ .withMessage(message)
+ .usingClock(FIXED_CLOCK)
+ .build();
+ }
+
+ @Test
+ public void constructorWithRegistryAsArgumentIsUsedInPreferenceOverServletConfig() throws Exception {
+ final HealthCheckRegistry healthCheckRegistry = mock(HealthCheckRegistry.class);
+ final ServletContext servletContext = mock(ServletContext.class);
+ final ServletConfig servletConfig = mock(ServletConfig.class);
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+
+ final io.dropwizard.metrics.servlets.HealthCheckServlet healthCheckServlet = new io.dropwizard.metrics.servlets.HealthCheckServlet(healthCheckRegistry);
+ healthCheckServlet.init(servletConfig);
+
+ verify(servletConfig, times(1)).getServletContext();
+ verify(servletContext, never()).getAttribute(eq(io.dropwizard.metrics.servlets.HealthCheckServlet.HEALTH_CHECK_REGISTRY));
+ }
+
+ @Test
+ public void constructorWithRegistryAsArgumentUsesServletConfigWhenNull() throws Exception {
+ final HealthCheckRegistry healthCheckRegistry = mock(HealthCheckRegistry.class);
+ final ServletContext servletContext = mock(ServletContext.class);
+ final ServletConfig servletConfig = mock(ServletConfig.class);
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute(eq(io.dropwizard.metrics.servlets.HealthCheckServlet.HEALTH_CHECK_REGISTRY)))
+ .thenReturn(healthCheckRegistry);
+
+ final io.dropwizard.metrics.servlets.HealthCheckServlet healthCheckServlet = new io.dropwizard.metrics.servlets.HealthCheckServlet(null);
+ healthCheckServlet.init(servletConfig);
+
+ verify(servletConfig, times(1)).getServletContext();
+ verify(servletContext, times(1)).getAttribute(eq(io.dropwizard.metrics.servlets.HealthCheckServlet.HEALTH_CHECK_REGISTRY));
+ }
+
+ @Test(expected = ServletException.class)
+ public void constructorWithRegistryAsArgumentUsesServletConfigWhenNullButWrongTypeInContext() throws Exception {
+ final ServletContext servletContext = mock(ServletContext.class);
+ final ServletConfig servletConfig = mock(ServletConfig.class);
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute(eq(io.dropwizard.metrics.servlets.HealthCheckServlet.HEALTH_CHECK_REGISTRY)))
+ .thenReturn("IRELLEVANT_STRING");
+
+ final io.dropwizard.metrics.servlets.HealthCheckServlet healthCheckServlet = new HealthCheckServlet(null);
+ healthCheckServlet.init(servletConfig);
+ }
+
+ @Test
+ public void constructorWithObjectMapperAsArgumentUsesServletConfigWhenNullButWrongTypeInContext() throws Exception {
+ final ServletContext servletContext = mock(ServletContext.class);
+ final ServletConfig servletConfig = mock(ServletConfig.class);
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY)).thenReturn(registry);
+ when(servletContext.getAttribute(HealthCheckServlet.HEALTH_CHECK_MAPPER)).thenReturn("IRELLEVANT_STRING");
+
+ final HealthCheckServlet healthCheckServlet = new HealthCheckServlet(null);
+ healthCheckServlet.init(servletConfig);
+
+ assertThat(healthCheckServlet.getMapper())
+ .isNotNull()
+ .isInstanceOf(ObjectMapper.class);
+ }
+
+ static class TestHealthCheck extends HealthCheck {
+ private final Callable<Result> check;
+
+ public TestHealthCheck(Callable<Result> check) {
+ this.check = check;
+ }
+
+ @Override
+ protected Result check() throws Exception {
+ return check.call();
+ }
+
+ @Override
+ protected Clock clock() {
+ return FIXED_CLOCK;
+ }
+ }
+
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/MetricsServletContextListenerTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/MetricsServletContextListenerTest.java
new file mode 100644
index 0000000..49ffb1c
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/MetricsServletContextListenerTest.java
@@ -0,0 +1,171 @@
+package io.dropwizard.metrics.servlets;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.Before;
+import org.junit.Test;
+
+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;
+
+public class MetricsServletContextListenerTest extends AbstractServletTest {
+ private final Clock clock = mock(Clock.class);
+ private final MetricRegistry registry = new MetricRegistry();
+ private final String allowedOrigin = "some.other.origin";
+
+ @Override
+ protected void setUp(ServletTester tester) {
+ tester.setAttribute("io.dropwizard.metrics.servlets.MetricsServlet.registry", registry);
+ tester.addServlet(io.dropwizard.metrics.servlets.MetricsServlet.class, "/metrics");
+ tester.getContext().addEventListener(new MetricsServlet.ContextListener() {
+ @Override
+ protected MetricRegistry getMetricRegistry() {
+ return registry;
+ }
+
+ @Override
+ protected TimeUnit getDurationUnit() {
+ return TimeUnit.MILLISECONDS;
+ }
+
+ @Override
+ protected TimeUnit getRateUnit() {
+ return TimeUnit.MINUTES;
+ }
+
+ @Override
+ protected String getAllowedOrigin() {
+ return allowedOrigin;
+ }
+ });
+ }
+
+ @Before
+ public void setUp() {
+ // provide ticks for the setup (calls getTick 6 times). The serialization in the tests themselves
+ // will call getTick again several times and always get the same value (the last specified here)
+ when(clock.getTick()).thenReturn(100L, 100L, 200L, 300L, 300L, 400L);
+
+ registry.register("g1", (Gauge<Long>) () -> 100L);
+ registry.counter("c").inc();
+ registry.histogram("h").update(1);
+ registry.register("m", new Meter(clock)).mark();
+ registry.register("t", new Timer(new ExponentiallyDecayingReservoir(), clock))
+ .update(1, TimeUnit.SECONDS);
+
+ request.setMethod("GET");
+ request.setURI("/metrics");
+ request.setVersion("HTTP/1.0");
+ }
+
+ @Test
+ public void returnsA200() throws Exception {
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.get("Access-Control-Allow-Origin"))
+ .isEqualTo(allowedOrigin);
+ assertThat(response.getContent())
+ .isEqualTo("{" +
+ "\"version\":\"4.0.0\"," +
+ "\"gauges\":{" +
+ "\"g1\":{\"value\":100}" +
+ "}," +
+ "\"counters\":{" +
+ "\"c\":{\"count\":1}" +
+ "}," +
+ "\"histograms\":{" +
+ "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
+ "}," +
+ "\"meters\":{" +
+ "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":2.0E8,\"units\":\"events/minute\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1000.0,\"mean\":1000.0,\"min\":1000.0,\"p50\":1000.0,\"p75\":1000.0,\"p95\":1000.0,\"p98\":1000.0,\"p99\":1000.0,\"p999\":1000.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":6.0E8,\"duration_units\":\"milliseconds\",\"rate_units\":\"calls/minute\"}" +
+ "}" +
+ "}");
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("application/json");
+ }
+
+ @Test
+ public void optionallyPrettyPrintsTheJson() throws Exception {
+ request.setURI("/metrics?pretty=true");
+
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.get("Access-Control-Allow-Origin"))
+ .isEqualTo(allowedOrigin);
+ assertThat(response.getContent())
+ .isEqualTo(String.format("{%n" +
+ " \"version\" : \"4.0.0\",%n" +
+ " \"gauges\" : {%n" +
+ " \"g1\" : {%n" +
+ " \"value\" : 100%n" +
+ " }%n" +
+ " },%n" +
+ " \"counters\" : {%n" +
+ " \"c\" : {%n" +
+ " \"count\" : 1%n" +
+ " }%n" +
+ " },%n" +
+ " \"histograms\" : {%n" +
+ " \"h\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"max\" : 1,%n" +
+ " \"mean\" : 1.0,%n" +
+ " \"min\" : 1,%n" +
+ " \"p50\" : 1.0,%n" +
+ " \"p75\" : 1.0,%n" +
+ " \"p95\" : 1.0,%n" +
+ " \"p98\" : 1.0,%n" +
+ " \"p99\" : 1.0,%n" +
+ " \"p999\" : 1.0,%n" +
+ " \"stddev\" : 0.0%n" +
+ " }%n" +
+ " },%n" +
+ " \"meters\" : {%n" +
+ " \"m\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"m15_rate\" : 0.0,%n" +
+ " \"m1_rate\" : 0.0,%n" +
+ " \"m5_rate\" : 0.0,%n" +
+ " \"mean_rate\" : 2.0E8,%n" +
+ " \"units\" : \"events/minute\"%n" +
+ " }%n" +
+ " },%n" +
+ " \"timers\" : {%n" +
+ " \"t\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"max\" : 1000.0,%n" +
+ " \"mean\" : 1000.0,%n" +
+ " \"min\" : 1000.0,%n" +
+ " \"p50\" : 1000.0,%n" +
+ " \"p75\" : 1000.0,%n" +
+ " \"p95\" : 1000.0,%n" +
+ " \"p98\" : 1000.0,%n" +
+ " \"p99\" : 1000.0,%n" +
+ " \"p999\" : 1000.0,%n" +
+ " \"stddev\" : 0.0,%n" +
+ " \"m15_rate\" : 0.0,%n" +
+ " \"m1_rate\" : 0.0,%n" +
+ " \"m5_rate\" : 0.0,%n" +
+ " \"mean_rate\" : 6.0E8,%n" +
+ " \"duration_units\" : \"milliseconds\",%n" +
+ " \"rate_units\" : \"calls/minute\"%n" +
+ " }%n" +
+ " }%n" +
+ "}"));
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("application/json");
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/MetricsServletTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/MetricsServletTest.java
new file mode 100644
index 0000000..c70a16f
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/MetricsServletTest.java
@@ -0,0 +1,263 @@
+package io.dropwizard.metrics.servlets;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class MetricsServletTest extends AbstractServletTest {
+ private final Clock clock = mock(Clock.class);
+ private final MetricRegistry registry = new MetricRegistry();
+ private ServletTester tester;
+
+ @Override
+ protected void setUp(ServletTester tester) {
+ this.tester = tester;
+ tester.setAttribute("io.dropwizard.metrics.servlets.MetricsServlet.registry", registry);
+ tester.addServlet(io.dropwizard.metrics.servlets.MetricsServlet.class, "/metrics");
+ tester.getContext().setInitParameter("io.dropwizard.metrics.servlets.MetricsServlet.allowedOrigin", "*");
+ }
+
+ @Before
+ public void setUp() {
+ // provide ticks for the setup (calls getTick 6 times). The serialization in the tests themselves
+ // will call getTick again several times and always get the same value (the last specified here)
+ when(clock.getTick()).thenReturn(100L, 100L, 200L, 300L, 300L, 400L);
+
+ registry.register("g1", (Gauge<Long>) () -> 100L);
+ registry.counter("c").inc();
+ registry.histogram("h").update(1);
+ registry.register("m", new Meter(clock)).mark();
+ registry.register("t", new Timer(new ExponentiallyDecayingReservoir(), clock))
+ .update(1, TimeUnit.SECONDS);
+
+ request.setMethod("GET");
+ request.setURI("/metrics");
+ request.setVersion("HTTP/1.0");
+ }
+
+ @Test
+ public void returnsA200() throws Exception {
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.get("Access-Control-Allow-Origin"))
+ .isEqualTo("*");
+ assertThat(response.getContent())
+ .isEqualTo("{" +
+ "\"version\":\"4.0.0\"," +
+ "\"gauges\":{" +
+ "\"g1\":{\"value\":100}" +
+ "}," +
+ "\"counters\":{" +
+ "\"c\":{\"count\":1}" +
+ "}," +
+ "\"histograms\":{" +
+ "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
+ "}," +
+ "\"meters\":{" +
+ "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
+ "}" +
+ "}");
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("application/json");
+ }
+
+ @Test
+ public void returnsJsonWhenJsonpInitParamNotSet() throws Exception {
+ String callbackParamName = "callbackParam";
+ String callbackParamVal = "callbackParamVal";
+ request.setURI("/metrics?" + callbackParamName + "=" + callbackParamVal);
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.get("Access-Control-Allow-Origin"))
+ .isEqualTo("*");
+ assertThat(response.getContent())
+ .isEqualTo("{" +
+ "\"version\":\"4.0.0\"," +
+ "\"gauges\":{" +
+ "\"g1\":{\"value\":100}" +
+ "}," +
+ "\"counters\":{" +
+ "\"c\":{\"count\":1}" +
+ "}," +
+ "\"histograms\":{" +
+ "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
+ "}," +
+ "\"meters\":{" +
+ "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
+ "}" +
+ "}");
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("application/json");
+ }
+
+ @Test
+ public void returnsJsonpWhenInitParamSet() throws Exception {
+ String callbackParamName = "callbackParam";
+ String callbackParamVal = "callbackParamVal";
+ request.setURI("/metrics?" + callbackParamName + "=" + callbackParamVal);
+ tester.getContext().setInitParameter("io.dropwizard.metrics.servlets.MetricsServlet.jsonpCallback", callbackParamName);
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get("Access-Control-Allow-Origin"))
+ .isEqualTo("*");
+ assertThat(response.getContent())
+ .isEqualTo(callbackParamVal + "({" +
+ "\"version\":\"4.0.0\"," +
+ "\"gauges\":{" +
+ "\"g1\":{\"value\":100}" +
+ "}," +
+ "\"counters\":{" +
+ "\"c\":{\"count\":1}" +
+ "}," +
+ "\"histograms\":{" +
+ "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
+ "}," +
+ "\"meters\":{" +
+ "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
+ "}" +
+ "})");
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("application/json");
+ }
+
+ @Test
+ public void optionallyPrettyPrintsTheJson() throws Exception {
+ request.setURI("/metrics?pretty=true");
+
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.get("Access-Control-Allow-Origin"))
+ .isEqualTo("*");
+ assertThat(response.getContent())
+ .isEqualTo(String.format("{%n" +
+ " \"version\" : \"4.0.0\",%n" +
+ " \"gauges\" : {%n" +
+ " \"g1\" : {%n" +
+ " \"value\" : 100%n" +
+ " }%n" +
+ " },%n" +
+ " \"counters\" : {%n" +
+ " \"c\" : {%n" +
+ " \"count\" : 1%n" +
+ " }%n" +
+ " },%n" +
+ " \"histograms\" : {%n" +
+ " \"h\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"max\" : 1,%n" +
+ " \"mean\" : 1.0,%n" +
+ " \"min\" : 1,%n" +
+ " \"p50\" : 1.0,%n" +
+ " \"p75\" : 1.0,%n" +
+ " \"p95\" : 1.0,%n" +
+ " \"p98\" : 1.0,%n" +
+ " \"p99\" : 1.0,%n" +
+ " \"p999\" : 1.0,%n" +
+ " \"stddev\" : 0.0%n" +
+ " }%n" +
+ " },%n" +
+ " \"meters\" : {%n" +
+ " \"m\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"m15_rate\" : 0.0,%n" +
+ " \"m1_rate\" : 0.0,%n" +
+ " \"m5_rate\" : 0.0,%n" +
+ " \"mean_rate\" : 3333333.3333333335,%n" +
+ " \"units\" : \"events/second\"%n" +
+ " }%n" +
+ " },%n" +
+ " \"timers\" : {%n" +
+ " \"t\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"max\" : 1.0,%n" +
+ " \"mean\" : 1.0,%n" +
+ " \"min\" : 1.0,%n" +
+ " \"p50\" : 1.0,%n" +
+ " \"p75\" : 1.0,%n" +
+ " \"p95\" : 1.0,%n" +
+ " \"p98\" : 1.0,%n" +
+ " \"p99\" : 1.0,%n" +
+ " \"p999\" : 1.0,%n" +
+ " \"stddev\" : 0.0,%n" +
+ " \"m15_rate\" : 0.0,%n" +
+ " \"m1_rate\" : 0.0,%n" +
+ " \"m5_rate\" : 0.0,%n" +
+ " \"mean_rate\" : 1.0E7,%n" +
+ " \"duration_units\" : \"seconds\",%n" +
+ " \"rate_units\" : \"calls/second\"%n" +
+ " }%n" +
+ " }%n" +
+ "}"));
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("application/json");
+ }
+
+ @Test
+ public void constructorWithRegistryAsArgumentIsUsedInPreferenceOverServletConfig() throws Exception {
+ final MetricRegistry metricRegistry = mock(MetricRegistry.class);
+ final ServletContext servletContext = mock(ServletContext.class);
+ final ServletConfig servletConfig = mock(ServletConfig.class);
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+
+ final io.dropwizard.metrics.servlets.MetricsServlet metricsServlet = new io.dropwizard.metrics.servlets.MetricsServlet(metricRegistry);
+ metricsServlet.init(servletConfig);
+
+ verify(servletConfig, times(1)).getServletContext();
+ verify(servletContext, never()).getAttribute(eq(io.dropwizard.metrics.servlets.MetricsServlet.METRICS_REGISTRY));
+ }
+
+ @Test
+ public void constructorWithRegistryAsArgumentUsesServletConfigWhenNull() throws Exception {
+ final MetricRegistry metricRegistry = mock(MetricRegistry.class);
+ final ServletContext servletContext = mock(ServletContext.class);
+ final ServletConfig servletConfig = mock(ServletConfig.class);
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute(eq(io.dropwizard.metrics.servlets.MetricsServlet.METRICS_REGISTRY)))
+ .thenReturn(metricRegistry);
+
+ final io.dropwizard.metrics.servlets.MetricsServlet metricsServlet = new io.dropwizard.metrics.servlets.MetricsServlet(null);
+ metricsServlet.init(servletConfig);
+
+ verify(servletConfig, times(1)).getServletContext();
+ verify(servletContext, times(1)).getAttribute(eq(io.dropwizard.metrics.servlets.MetricsServlet.METRICS_REGISTRY));
+ }
+
+ @Test(expected = ServletException.class)
+ public void constructorWithRegistryAsArgumentUsesServletConfigWhenNullButWrongTypeInContext() throws Exception {
+ final ServletContext servletContext = mock(ServletContext.class);
+ final ServletConfig servletConfig = mock(ServletConfig.class);
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute(eq(io.dropwizard.metrics.servlets.MetricsServlet.METRICS_REGISTRY)))
+ .thenReturn("IRELLEVANT_STRING");
+
+ final io.dropwizard.metrics.servlets.MetricsServlet metricsServlet = new MetricsServlet(null);
+ metricsServlet.init(servletConfig);
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/PingServletTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/PingServletTest.java
new file mode 100644
index 0000000..a068560
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/PingServletTest.java
@@ -0,0 +1,49 @@
+package io.dropwizard.metrics.servlets;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PingServletTest extends AbstractServletTest {
+ @Override
+ protected void setUp(ServletTester tester) {
+ tester.addServlet(PingServlet.class, "/ping");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ request.setMethod("GET");
+ request.setURI("/ping");
+ request.setVersion("HTTP/1.0");
+
+ processRequest();
+ }
+
+ @Test
+ public void returns200OK() {
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ }
+
+ @Test
+ public void returnsPong() {
+ assertThat(response.getContent())
+ .isEqualTo(String.format("pong%n"));
+ }
+
+ @Test
+ public void returnsTextPlain() {
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("text/plain;charset=ISO-8859-1");
+ }
+
+ @Test
+ public void returnsUncacheable() {
+ assertThat(response.get(HttpHeader.CACHE_CONTROL))
+ .isEqualTo("must-revalidate,no-cache,no-store");
+
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/ThreadDumpServletTest.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/ThreadDumpServletTest.java
new file mode 100644
index 0000000..af4db51
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/ThreadDumpServletTest.java
@@ -0,0 +1,49 @@
+package io.dropwizard.metrics.servlets;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ThreadDumpServletTest extends AbstractServletTest {
+ @Override
+ protected void setUp(ServletTester tester) {
+ tester.addServlet(ThreadDumpServlet.class, "/threads");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ request.setMethod("GET");
+ request.setURI("/threads");
+ request.setVersion("HTTP/1.0");
+
+ processRequest();
+ }
+
+ @Test
+ public void returns200OK() {
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ }
+
+ @Test
+ public void returnsAThreadDump() {
+ assertThat(response.getContent())
+ .contains("Finalizer");
+ }
+
+ @Test
+ public void returnsTextPlain() {
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("text/plain");
+ }
+
+ @Test
+ public void returnsUncacheable() {
+ assertThat(response.get(HttpHeader.CACHE_CONTROL))
+ .isEqualTo("must-revalidate,no-cache,no-store");
+
+ }
+}
diff --git a/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/experiments/ExampleServer.java b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/experiments/ExampleServer.java
new file mode 100644
index 0000000..5fb5db4
--- /dev/null
+++ b/metrics-jakarta-servlets/src/test/java/io/dropwizard/metrics/servlets/experiments/ExampleServer.java
@@ -0,0 +1,60 @@
+package io.dropwizard.metrics.servlets.experiments;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import io.dropwizard.metrics.jetty11.InstrumentedConnectionFactory;
+import io.dropwizard.metrics.jetty11.InstrumentedHandler;
+import io.dropwizard.metrics.jetty11.InstrumentedQueuedThreadPool;
+import io.dropwizard.metrics.servlets.AdminServlet;
+import io.dropwizard.metrics.servlets.HealthCheckServlet;
+import io.dropwizard.metrics.servlets.MetricsServlet;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+public class ExampleServer {
+ private static final MetricRegistry REGISTRY = new MetricRegistry();
+ private static final Counter COUNTER_1 = REGISTRY.counter(name(ExampleServer.class, "wah", "doody"));
+ private static final Counter COUNTER_2 = REGISTRY.counter(name(ExampleServer.class, "woo"));
+
+ static {
+ REGISTRY.register(name(ExampleServer.class, "boo"), (Gauge<Integer>) () -> {
+ throw new RuntimeException("asplode!");
+ });
+ }
+
+ public static void main(String[] args) throws Exception {
+ COUNTER_1.inc();
+ COUNTER_2.inc();
+
+ final ThreadPool threadPool = new InstrumentedQueuedThreadPool(REGISTRY);
+ final Server server = new Server(threadPool);
+
+ final Connector connector = new ServerConnector(server, new InstrumentedConnectionFactory(
+ new HttpConnectionFactory(), REGISTRY.timer("http.connection")));
+ server.addConnector(connector);
+
+ final ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/initial");
+ context.setAttribute(MetricsServlet.METRICS_REGISTRY, REGISTRY);
+ context.setAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY, new HealthCheckRegistry());
+
+ final ServletHolder holder = new ServletHolder(new AdminServlet());
+ context.addServlet(holder, "/dingo/*");
+
+ final InstrumentedHandler handler = new InstrumentedHandler(REGISTRY);
+ handler.setHandler(context);
+ server.setHandler(handler);
+
+ server.start();
+ server.join();
+ }
+}
diff --git a/metrics-jcache/pom.xml b/metrics-jcache/pom.xml
index ec2ac54..5594dfb 100644
--- a/metrics-jcache/pom.xml
+++ b/metrics-jcache/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-parent</artifactId>
- <version>3.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-jcache</artifactId>
@@ -16,22 +16,98 @@
Uses the CacheStatisticsMXBean provided statistics.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.jcache</javaModuleName>
+ <cache-api.version>1.1.1</cache-api.version>
+ <ehcache3.version>3.10.8</ehcache3.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jvm</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
- <version>1.0.0</version>
+ <version>${cache-api.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
- <version>3.1.3</version>
+ <version>${ehcache3.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.jaxb</groupId>
+ <artifactId>jaxb-runtime</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.cache</groupId>
+ <artifactId>cache-api</artifactId>
+ </exclusion>
+ </exclusions>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.xml.bind</groupId>
+ <artifactId>jaxb-api</artifactId>
+ <version>2.3.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jaxb</groupId>
+ <artifactId>jaxb-runtime</artifactId>
+ <version>2.3.9</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.activation</groupId>
+ <artifactId>activation</artifactId>
+ <version>1.1.1</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
index e21e844..47634fa 100644
--- a/metrics-jcache/src/main/java/com/codahale/metrics/jcache/JCacheGaugeSet.java
+++ b/metrics-jcache/src/main/java/com/codahale/metrics/jcache/JCacheGaugeSet.java
@@ -3,7 +3,7 @@ package com.codahale.metrics.jcache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.codahale.metrics.JmxAttributeGauge;
+import com.codahale.metrics.jvm.JmxAttributeGauge;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricSet;
@@ -41,7 +41,7 @@ public class JCacheGaugeSet implements MetricSet {
Set<ObjectInstance> cacheBeans = getCacheBeans();
List<String> availableStatsNames = retrieveStatsNames();
- Map<String, Metric> gauges = new HashMap<String, Metric>(cacheBeans.size() * availableStatsNames.size());
+ Map<String, Metric> gauges = new HashMap<>(cacheBeans.size() * availableStatsNames.size());
for (ObjectInstance cacheBean : cacheBeans) {
ObjectName objectName = cacheBean.getObjectName();
@@ -59,8 +59,7 @@ public class JCacheGaugeSet implements MetricSet {
private Set<ObjectInstance> getCacheBeans() {
try {
return ManagementFactory.getPlatformMBeanServer().queryMBeans(ObjectName.getInstance(M_BEAN_COORDINATES), null);
- }
- catch(MalformedObjectNameException e) {
+ } catch (MalformedObjectNameException e) {
LOGGER.error("Unable to retrieve {}. Are JCache statistics enabled?", M_BEAN_COORDINATES);
throw new RuntimeException(e);
}
@@ -68,11 +67,11 @@ public class JCacheGaugeSet implements MetricSet {
private List<String> retrieveStatsNames() {
Method[] methods = CacheStatisticsMXBean.class.getDeclaredMethods();
- List<String> availableStatsNames = new ArrayList<String>(methods.length);
+ List<String> availableStatsNames = new ArrayList<>(methods.length);
for (Method method : methods) {
String methodName = method.getName();
- if(methodName.startsWith("get")) {
+ if (methodName.startsWith("get")) {
availableStatsNames.add(methodName.substring(3));
}
}
diff --git a/metrics-jcache/src/test/java/JCacheGaugeSetTest.java b/metrics-jcache/src/test/java/JCacheGaugeSetTest.java
index c331c58..48a026e 100644
--- a/metrics-jcache/src/test/java/JCacheGaugeSetTest.java
+++ b/metrics-jcache/src/test/java/JCacheGaugeSetTest.java
@@ -24,8 +24,8 @@ public class JCacheGaugeSetTest {
CachingProvider provider = Caching.getCachingProvider();
cacheManager = provider.getCacheManager(
- getClass().getResource("ehcache.xml").toURI(),
- getClass().getClassLoader());
+ getClass().getResource("ehcache.xml").toURI(),
+ getClass().getClassLoader());
myCache = cacheManager.getCache("myCache");
myOtherCache = cacheManager.getCache("myOtherCache");
@@ -39,41 +39,41 @@ public class JCacheGaugeSetTest {
myOtherCache.get("woo");
assertThat(registry.getGauges().get("jcache.statistics.myOtherCache.cache-misses").getValue())
- .isEqualTo(1L);
+ .isEqualTo(1L);
myCache.get("woo");
assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-misses").getValue())
- .isEqualTo(1L);
+ .isEqualTo(1L);
assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-hits").getValue())
- .isEqualTo(0L);
+ .isEqualTo(0L);
assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-gets").getValue())
- .isEqualTo(1L);
+ .isEqualTo(1L);
myCache.put("woo", "whee");
myCache.get("woo");
assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-puts").getValue())
- .isEqualTo(1L);
+ .isEqualTo(1L);
assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-hits").getValue())
- .isEqualTo(1L);
+ .isEqualTo(1L);
assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-hit-percentage").getValue())
- .isEqualTo(50.0f);
+ .isEqualTo(50.0f);
assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-miss-percentage").getValue())
- .isEqualTo(50.0f);
+ .isEqualTo(50.0f);
assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-gets").getValue())
- .isEqualTo(2L);
+ .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);
+ .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);
+ 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);
}
diff --git a/metrics-jcache/src/test/resources/ehcache.xml b/metrics-jcache/src/test/resources/ehcache.xml
index eacfd2b..048f260 100644
--- a/metrics-jcache/src/test/resources/ehcache.xml
+++ b/metrics-jcache/src/test/resources/ehcache.xml
@@ -1,16 +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>
+ <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"/>
+ <cache alias="myCache" uses-template="simple"/>
+ <cache alias="myOtherCache" uses-template="simple"/>
</config>
\ No newline at end of file
diff --git a/metrics-jcstress/findbugs-exclude.xml b/metrics-jcstress/findbugs-exclude.xml
deleted file mode 100644
index 097bbe3..0000000
--- a/metrics-jcstress/findbugs-exclude.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<FindBugsFilter>
- <Match>
- <Package name="com.codahale.metrics" />
- </Match>
- <Match>
- <Package name="org.openjdk.jcstress.infra.results" />
- </Match>
-</FindBugsFilter>
diff --git a/metrics-jcstress/pom.xml b/metrics-jcstress/pom.xml
index 1fba6f9..7c9ace5 100644
--- a/metrics-jcstress/pom.xml
+++ b/metrics-jcstress/pom.xml
@@ -4,11 +4,10 @@
<parent>
<artifactId>metrics-parent</artifactId>
<groupId>io.dropwizard.metrics</groupId>
- <version>3.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-jcstress</artifactId>
- <version>3.2.6</version>
<packaging>jar</packaging>
<name>Metrics JCStress tests</name>
@@ -18,10 +17,6 @@
Edit as needed.
-->
- <prerequisites>
- <maven>3.0</maven>
- </prerequisites>
-
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
@@ -41,7 +36,7 @@
<!--
jcstress version to use with this project.
-->
- <jcstress.version>0.1.1</jcstress.version>
+ <jcstress.version>0.16</jcstress.version>
<!--
Java source/target to use for compilation.
@@ -52,34 +47,19 @@
Name of the test Uber-JAR to generate.
-->
<uberjar.name>jcstress</uberjar.name>
+
+ <javaModuleName>com.codahale.metrics.jcstress</javaModuleName>
+ <jar.skipIfEmpty>true</jar.skipIfEmpty>
+ <maven.install.skip>true</maven.install.skip>
+ <maven.deploy.skip>true</maven.deploy.skip>
</properties>
<build>
<plugins>
- <plugin>
- <!-- don't deploy this -->
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-deploy-plugin</artifactId>
- <version>2.7</version>
- <configuration>
- <skip>true</skip>
- </configuration>
- </plugin>
- <plugin>
- <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>
+ <version>3.5.1</version>
<executions>
<execution>
<id>main</id>
@@ -101,14 +81,6 @@
</execution>
</executions>
</plugin>
- <plugin>
- <!-- exclude jmh generated classes designed to trick jvm like "unused fields" padding -->
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <configuration>
- <excludeFilterFile>findbugs-exclude.xml</excludeFilterFile>
- </configuration>
- </plugin>
</plugins>
</build>
diff --git a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTrimReadTest.java b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTrimReadTest.java
index 3132265..458aefc 100644
--- a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTrimReadTest.java
+++ b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTrimReadTest.java
@@ -5,7 +5,7 @@ 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 org.openjdk.jcstress.infra.results.L_Result;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@@ -16,23 +16,23 @@ import java.util.concurrent.atomic.AtomicLong;
id = "\\[240, 241, 242, 243, 244, 245, 246, 247, 248, 249\\]",
expect = Expect.ACCEPTABLE,
desc = "Actor1 made read before Actor2 even started"
-)
+ )
@Outcome(
id = "\\[243, 244, 245, 246, 247, 248, 249\\]",
expect = Expect.ACCEPTABLE,
desc = "Actor2 made trim before Actor1 even started"
-)
+ )
@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."
-)
+ )
@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."
-)
+ )
@State
public class SlidingTimeWindowArrayReservoirTrimReadTest {
private final AtomicLong ticks = new AtomicLong(0);
@@ -53,7 +53,7 @@ public class SlidingTimeWindowArrayReservoirTrimReadTest {
}
@Actor
- public void actor1(StringResult1 r) {
+ public void actor1(L_Result r) {
Snapshot snapshot = reservoir.getSnapshot();
String stringValues = Arrays.toString(snapshot.getValues());
r.r1 = stringValues;
diff --git a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadAllocate.java b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadAllocate.java
index 8b73a89..8c6883b 100644
--- a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadAllocate.java
+++ b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadAllocate.java
@@ -6,7 +6,7 @@ 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 org.openjdk.jcstress.infra.results.L_Result;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@@ -36,7 +36,7 @@ public class SlidingTimeWindowArrayReservoirWriteReadAllocate {
}
@Arbiter
- public void arbiter(StringResult1 r) {
+ public void arbiter(L_Result r) {
Snapshot snapshot = reservoir.getSnapshot();
long[] values = snapshot.getValues();
String stringValues = Arrays.toString(Arrays.copyOfRange(values, values.length - 3, values.length));
diff --git a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadTest.java b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadTest.java
index 420770d..82d78ea 100644
--- a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadTest.java
+++ b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadTest.java
@@ -5,7 +5,7 @@ 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 org.openjdk.jcstress.infra.results.L_Result;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@@ -36,7 +36,7 @@ public class SlidingTimeWindowArrayReservoirWriteReadTest {
}
@Actor
- public void actor3(StringResult1 r) {
+ public void actor3(L_Result r) {
Snapshot snapshot = reservoir.getSnapshot();
String stringValues = Arrays.toString(snapshot.getValues());
r.r1 = stringValues;
diff --git a/metrics-jdbi/pom.xml b/metrics-jdbi/pom.xml
index b519d72..390c13c 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-jdbi</artifactId>
@@ -15,16 +15,61 @@
A JDBI wrapper providing Metrics instrumentation of query durations and rates.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.jdbi</javaModuleName>
+ <jdbi2.version>2.78</jdbi2.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
- <version>2.55</version>
+ <version>${jdbi2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
</dependency>
</dependencies>
</project>
diff --git a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/BasicSqlNameStrategy.java b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/BasicSqlNameStrategy.java
index 5f60bad..9c9fa70 100644
--- a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/BasicSqlNameStrategy.java
+++ b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/BasicSqlNameStrategy.java
@@ -3,6 +3,6 @@ package com.codahale.metrics.jdbi.strategies;
public class BasicSqlNameStrategy extends DelegatingStatementNameStrategy {
public BasicSqlNameStrategy() {
super(NameStrategies.CHECK_EMPTY,
- NameStrategies.SQL_OBJECT);
+ NameStrategies.SQL_OBJECT);
}
}
diff --git a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/ContextNameStrategy.java b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/ContextNameStrategy.java
index 849f6fc..2d2e33a 100644
--- a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/ContextNameStrategy.java
+++ b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/ContextNameStrategy.java
@@ -8,8 +8,8 @@ package com.codahale.metrics.jdbi.strategies;
public class ContextNameStrategy extends DelegatingStatementNameStrategy {
public ContextNameStrategy() {
super(NameStrategies.CHECK_EMPTY,
- NameStrategies.CHECK_RAW,
- NameStrategies.CONTEXT_NAME,
- NameStrategies.NAIVE_NAME);
+ NameStrategies.CHECK_RAW,
+ NameStrategies.CONTEXT_NAME,
+ NameStrategies.NAIVE_NAME);
}
}
diff --git a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/DelegatingStatementNameStrategy.java b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/DelegatingStatementNameStrategy.java
index 38c565f..7551d55 100644
--- a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/DelegatingStatementNameStrategy.java
+++ b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/DelegatingStatementNameStrategy.java
@@ -7,7 +7,7 @@ import java.util.Arrays;
import java.util.List;
public abstract class DelegatingStatementNameStrategy implements StatementNameStrategy {
- private final List<StatementNameStrategy> strategies = new ArrayList<StatementNameStrategy>();
+ private final List<StatementNameStrategy> strategies = new ArrayList<>();
protected DelegatingStatementNameStrategy(StatementNameStrategy... strategies) {
registerStrategies(strategies);
diff --git a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/NaiveNameStrategy.java b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/NaiveNameStrategy.java
index 6c2f811..8405ddd 100644
--- a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/NaiveNameStrategy.java
+++ b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/NaiveNameStrategy.java
@@ -6,7 +6,7 @@ package com.codahale.metrics.jdbi.strategies;
public class NaiveNameStrategy extends DelegatingStatementNameStrategy {
public NaiveNameStrategy() {
super(NameStrategies.CHECK_EMPTY,
- NameStrategies.CHECK_RAW,
- NameStrategies.NAIVE_NAME);
+ NameStrategies.CHECK_RAW,
+ NameStrategies.NAIVE_NAME);
}
}
diff --git a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/NameStrategies.java b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/NameStrategies.java
index 0885e01..b8064e5 100644
--- a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/NameStrategies.java
+++ b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/NameStrategies.java
@@ -145,8 +145,8 @@ public final class NameStrategies {
}
return name(className.substring(0, dotPos),
- className.substring(dotPos + 1),
- statementName);
+ className.substring(dotPos + 1),
+ statementName);
}
}
diff --git a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/ShortNameStrategy.java b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/ShortNameStrategy.java
index 9859d0a..232c824 100644
--- a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/ShortNameStrategy.java
+++ b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/ShortNameStrategy.java
@@ -13,7 +13,7 @@ import static com.codahale.metrics.MetricRegistry.name;
* by class name and method; a shortening strategy is applied to make the JMX output nicer.
*/
public final class ShortNameStrategy extends DelegatingStatementNameStrategy {
- private final ConcurrentMap<String, String> shortClassNames = new ConcurrentHashMap<String, String>();
+ private final ConcurrentMap<String, String> shortClassNames = new ConcurrentHashMap<>();
private final String baseJmxName;
@@ -23,10 +23,10 @@ public final class ShortNameStrategy extends DelegatingStatementNameStrategy {
// Java does not allow super (..., new ShortContextClassStrategy(), new ShortSqlObjectStrategy(), ...);
// ==> No enclosing instance of type <xxx> is available due to some intermediate constructor invocation. Lame.
registerStrategies(NameStrategies.CHECK_EMPTY,
- new ShortContextClassStrategy(),
- new ShortSqlObjectStrategy(),
- NameStrategies.CHECK_RAW,
- NameStrategies.NAIVE_NAME);
+ new ShortContextClassStrategy(),
+ new ShortSqlObjectStrategy(),
+ NameStrategies.CHECK_RAW,
+ NameStrategies.NAIVE_NAME);
}
private final class ShortContextClassStrategy implements StatementNameStrategy {
diff --git a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/SmartNameStrategy.java b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/SmartNameStrategy.java
index 9802497..b0948f6 100644
--- a/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/SmartNameStrategy.java
+++ b/metrics-jdbi/src/main/java/com/codahale/metrics/jdbi/strategies/SmartNameStrategy.java
@@ -5,16 +5,16 @@ package com.codahale.metrics.jdbi.strategies;
* Adds statistics for JDBI queries that set the {@link NameStrategies#STATEMENT_CLASS} and {@link
* NameStrategies#STATEMENT_NAME} for class based display or {@link NameStrategies#STATEMENT_GROUP}
* and {@link NameStrategies#STATEMENT_NAME} for group based display.
- * <p/>
+ * <p>
* Also knows how to deal with SQL Object statements.
*/
public class SmartNameStrategy extends DelegatingStatementNameStrategy {
public SmartNameStrategy() {
super(NameStrategies.CHECK_EMPTY,
- NameStrategies.CONTEXT_CLASS,
- NameStrategies.CONTEXT_NAME,
- NameStrategies.SQL_OBJECT,
- NameStrategies.CHECK_RAW,
- NameStrategies.NAIVE_NAME);
+ NameStrategies.CONTEXT_CLASS,
+ NameStrategies.CONTEXT_NAME,
+ NameStrategies.SQL_OBJECT,
+ NameStrategies.CHECK_RAW,
+ NameStrategies.NAIVE_NAME);
}
}
diff --git a/metrics-jdbi3/pom.xml b/metrics-jdbi3/pom.xml
new file mode 100644
index 0000000..3190352
--- /dev/null
+++ b/metrics-jdbi3/pom.xml
@@ -0,0 +1,86 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jdbi3</artifactId>
+ <name>Metrics Integration for JDBI3</name>
+ <packaging>bundle</packaging>
+ <description>Provides instrumentation of Jdbi3 data access objects</description>
+
+ <properties>
+ <javaModuleName>com.codahale.metrics.jdbi3</javaModuleName>
+ <jdbi3.version>3.43.0</jdbi3.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jdbi</groupId>
+ <artifactId>jdbi3-core</artifactId>
+ <version>${jdbi3.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/InstrumentedSqlLogger.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/InstrumentedSqlLogger.java
new file mode 100755
index 0000000..b3e6d9d
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/InstrumentedSqlLogger.java
@@ -0,0 +1,48 @@
+package com.codahale.metrics.jdbi3;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jdbi3.strategies.SmartNameStrategy;
+import com.codahale.metrics.jdbi3.strategies.StatementNameStrategy;
+import org.jdbi.v3.core.statement.SqlLogger;
+import org.jdbi.v3.core.statement.StatementContext;
+
+import java.sql.SQLException;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link SqlLogger} implementation for JDBI which uses the SQL objects' class names and
+ * method names for nanosecond-precision timers.
+ */
+public class InstrumentedSqlLogger implements SqlLogger {
+ private final MetricRegistry registry;
+ private final StatementNameStrategy statementNameStrategy;
+
+ public InstrumentedSqlLogger(MetricRegistry registry) {
+ this(registry, new SmartNameStrategy());
+ }
+
+ public InstrumentedSqlLogger(MetricRegistry registry,
+ StatementNameStrategy statementNameStrategy) {
+ this.registry = registry;
+ this.statementNameStrategy = statementNameStrategy;
+ }
+
+ @Override
+ public void logAfterExecution(StatementContext context) {
+ log(context);
+ }
+
+ @Override
+ public void logException(StatementContext context, SQLException ex) {
+ log(context);
+ }
+
+ private void log(StatementContext context) {
+ String statementName = statementNameStrategy.getStatementName(context);
+ if (statementName != null) {
+ final long elapsed = context.getElapsedTime(ChronoUnit.NANOS);
+ registry.timer(statementName).update(elapsed, TimeUnit.NANOSECONDS);
+ }
+ }
+}
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/InstrumentedTimingCollector.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/InstrumentedTimingCollector.java
new file mode 100644
index 0000000..80d03ac
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/InstrumentedTimingCollector.java
@@ -0,0 +1,41 @@
+package com.codahale.metrics.jdbi3;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jdbi3.strategies.SmartNameStrategy;
+import com.codahale.metrics.jdbi3.strategies.StatementNameStrategy;
+import org.jdbi.v3.core.statement.SqlLogger;
+import org.jdbi.v3.core.statement.StatementContext;
+import org.jdbi.v3.core.statement.TimingCollector;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link TimingCollector} implementation for JDBI which uses the SQL objects' class names and
+ * method names for millisecond-precision timers.
+ *
+ * @deprecated Use {@link InstrumentedSqlLogger} and {@link org.jdbi.v3.core.Jdbi#setSqlLogger(SqlLogger)} instead.
+ */
+@Deprecated
+public class InstrumentedTimingCollector implements TimingCollector {
+
+ private final MetricRegistry registry;
+ private final StatementNameStrategy statementNameStrategy;
+
+ public InstrumentedTimingCollector(MetricRegistry registry) {
+ this(registry, new SmartNameStrategy());
+ }
+
+ public InstrumentedTimingCollector(MetricRegistry registry,
+ StatementNameStrategy statementNameStrategy) {
+ this.registry = registry;
+ this.statementNameStrategy = statementNameStrategy;
+ }
+
+ @Override
+ public void collect(long elapsedTime, StatementContext ctx) {
+ String statementName = statementNameStrategy.getStatementName(ctx);
+ if (statementName != null) {
+ registry.timer(statementName).update(elapsedTime, TimeUnit.NANOSECONDS);
+ }
+ }
+}
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/BasicSqlNameStrategy.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/BasicSqlNameStrategy.java
new file mode 100644
index 0000000..4230493
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/BasicSqlNameStrategy.java
@@ -0,0 +1,12 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+/**
+ * Collects metrics by respective SQLObject methods.
+ */
+public class BasicSqlNameStrategy extends DelegatingStatementNameStrategy {
+
+ public BasicSqlNameStrategy() {
+ super(DefaultNameStrategy.CHECK_EMPTY,
+ DefaultNameStrategy.SQL_OBJECT);
+ }
+}
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/DefaultNameStrategy.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/DefaultNameStrategy.java
new file mode 100644
index 0000000..f6d5511
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/DefaultNameStrategy.java
@@ -0,0 +1,59 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import com.codahale.metrics.MetricRegistry;
+import org.jdbi.v3.core.extension.ExtensionMethod;
+import org.jdbi.v3.core.statement.StatementContext;
+
+/**
+ * Default strategies which build a basis of more complex strategies
+ */
+public enum DefaultNameStrategy implements StatementNameStrategy {
+
+ /**
+ * If no SQL in the context, returns `sql.empty`, otherwise falls through
+ */
+ CHECK_EMPTY {
+ @Override
+ public String getStatementName(StatementContext statementContext) {
+ final String rawSql = statementContext.getRawSql();
+ return rawSql == null || rawSql.isEmpty() ? "sql.empty" : null;
+ }
+ },
+
+ /**
+ * If there is an SQL object attached to the context, returns the name package,
+ * the class and the method on which SQL is declared. If not SQL object is attached,
+ * falls through
+ */
+ SQL_OBJECT {
+ @Override
+ public String getStatementName(StatementContext statementContext) {
+ ExtensionMethod extensionMethod = statementContext.getExtensionMethod();
+ if (extensionMethod != null) {
+ return MetricRegistry.name(extensionMethod.getType(), extensionMethod.getMethod().getName());
+ }
+ return null;
+ }
+ },
+
+ /**
+ * Returns a raw SQL in the context (even if it's not exist)
+ */
+ NAIVE_NAME {
+ @Override
+ public String getStatementName(StatementContext statementContext) {
+ return statementContext.getRawSql();
+ }
+ },
+
+ /**
+ * Returns the `sql.raw` constant
+ */
+ CONSTANT_SQL_RAW {
+ @Override
+ public String getStatementName(StatementContext statementContext) {
+ return "sql.raw";
+ }
+ }
+
+}
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/DelegatingStatementNameStrategy.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/DelegatingStatementNameStrategy.java
new file mode 100644
index 0000000..5e911e7
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/DelegatingStatementNameStrategy.java
@@ -0,0 +1,32 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import org.jdbi.v3.core.statement.StatementContext;
+
+import java.util.Arrays;
+import java.util.List;
+
+public abstract class DelegatingStatementNameStrategy implements StatementNameStrategy {
+
+ /**
+ * Unknown SQL.
+ */
+ private static final String UNKNOWN_SQL = "sql.unknown";
+
+ private final List<StatementNameStrategy> strategies;
+
+ protected DelegatingStatementNameStrategy(StatementNameStrategy... strategies) {
+ this.strategies = Arrays.asList(strategies);
+ }
+
+ @Override
+ public String getStatementName(StatementContext statementContext) {
+ for (StatementNameStrategy strategy : strategies) {
+ final String statementName = strategy.getStatementName(statementContext);
+ if (statementName != null) {
+ return statementName;
+ }
+ }
+
+ return UNKNOWN_SQL;
+ }
+}
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/NaiveNameStrategy.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/NaiveNameStrategy.java
new file mode 100644
index 0000000..9967504
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/NaiveNameStrategy.java
@@ -0,0 +1,12 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+/**
+ * Very simple strategy, can be used with any JDBI loader to build basic statistics.
+ */
+public class NaiveNameStrategy extends DelegatingStatementNameStrategy {
+
+ public NaiveNameStrategy() {
+ super(DefaultNameStrategy.CHECK_EMPTY,
+ DefaultNameStrategy.NAIVE_NAME);
+ }
+}
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/SmartNameStrategy.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/SmartNameStrategy.java
new file mode 100644
index 0000000..c42804e
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/SmartNameStrategy.java
@@ -0,0 +1,13 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+/**
+ * Uses a {@link BasicSqlNameStrategy} and fallbacks to {@link DefaultNameStrategy#CONSTANT_SQL_RAW}
+ */
+public class SmartNameStrategy extends DelegatingStatementNameStrategy {
+
+ public SmartNameStrategy() {
+ super(DefaultNameStrategy.CHECK_EMPTY,
+ DefaultNameStrategy.SQL_OBJECT,
+ DefaultNameStrategy.CONSTANT_SQL_RAW);
+ }
+}
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/StatementNameStrategy.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/StatementNameStrategy.java
new file mode 100644
index 0000000..d069eb3
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/StatementNameStrategy.java
@@ -0,0 +1,12 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import org.jdbi.v3.core.statement.StatementContext;
+
+/**
+ * Interface for strategies to statement contexts to metric names.
+ */
+@FunctionalInterface
+public interface StatementNameStrategy {
+
+ String getStatementName(StatementContext statementContext);
+}
diff --git a/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/TimedAnnotationNameStrategy.java b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/TimedAnnotationNameStrategy.java
new file mode 100644
index 0000000..e437444
--- /dev/null
+++ b/metrics-jdbi3/src/main/java/com/codahale/metrics/jdbi3/strategies/TimedAnnotationNameStrategy.java
@@ -0,0 +1,47 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.annotation.Timed;
+import org.jdbi.v3.core.extension.ExtensionMethod;
+import org.jdbi.v3.core.statement.StatementContext;
+
+import java.lang.reflect.Method;
+
+/**
+ * Takes into account the {@link Timed} annotation on extension methods
+ */
+public class TimedAnnotationNameStrategy implements StatementNameStrategy {
+
+ @Override
+ public String getStatementName(StatementContext statementContext) {
+ final ExtensionMethod extensionMethod = statementContext.getExtensionMethod();
+ if (extensionMethod == null) {
+ return null;
+ }
+
+ final Class<?> clazz = extensionMethod.getType();
+ final Timed classTimed = clazz.getAnnotation(Timed.class);
+ final Method method = extensionMethod.getMethod();
+ final Timed methodTimed = method.getAnnotation(Timed.class);
+
+ // If the method is timed, figure out the name
+ if (methodTimed != null) {
+ String methodName = methodTimed.name().isEmpty() ? method.getName() : methodTimed.name();
+ if (methodTimed.absolute()) {
+ return methodName;
+ } else {
+ // We need to check if the class has a custom timer name
+ return classTimed == null || classTimed.name().isEmpty() ?
+ MetricRegistry.name(clazz, methodName) :
+ MetricRegistry.name(classTimed.name(), methodName);
+ }
+ } else if (classTimed != null) {
+ // Maybe the class is timed?
+ return classTimed.name().isEmpty() ? MetricRegistry.name(clazz, method.getName()) :
+ MetricRegistry.name(classTimed.name(), method.getName());
+ } else {
+ // No timers neither on the method or the class
+ return null;
+ }
+ }
+}
diff --git a/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/InstrumentedSqlLoggerTest.java b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/InstrumentedSqlLoggerTest.java
new file mode 100755
index 0000000..f527fb2
--- /dev/null
+++ b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/InstrumentedSqlLoggerTest.java
@@ -0,0 +1,61 @@
+package com.codahale.metrics.jdbi3;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.jdbi3.strategies.StatementNameStrategy;
+import org.jdbi.v3.core.statement.StatementContext;
+import org.junit.Test;
+
+import java.sql.SQLException;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class InstrumentedSqlLoggerTest {
+ @Test
+ public void logsExecutionTime() {
+ final MetricRegistry mockRegistry = mock(MetricRegistry.class);
+ final StatementNameStrategy mockNameStrategy = mock(StatementNameStrategy.class);
+ final InstrumentedSqlLogger logger = new InstrumentedSqlLogger(mockRegistry, mockNameStrategy);
+
+ final StatementContext mockContext = mock(StatementContext.class);
+ final Timer mockTimer = mock(Timer.class);
+
+ final String statementName = "my-fake-name";
+ final long fakeElapsed = 1234L;
+
+ when(mockNameStrategy.getStatementName(mockContext)).thenReturn(statementName);
+ when(mockRegistry.timer(statementName)).thenReturn(mockTimer);
+
+ when(mockContext.getElapsedTime(ChronoUnit.NANOS)).thenReturn(fakeElapsed);
+
+ logger.logAfterExecution(mockContext);
+
+ verify(mockTimer).update(fakeElapsed, TimeUnit.NANOSECONDS);
+ }
+
+ @Test
+ public void logsExceptionTime() {
+ final MetricRegistry mockRegistry = mock(MetricRegistry.class);
+ final StatementNameStrategy mockNameStrategy = mock(StatementNameStrategy.class);
+ final InstrumentedSqlLogger logger = new InstrumentedSqlLogger(mockRegistry, mockNameStrategy);
+
+ final StatementContext mockContext = mock(StatementContext.class);
+ final Timer mockTimer = mock(Timer.class);
+
+ final String statementName = "my-fake-name";
+ final long fakeElapsed = 1234L;
+
+ when(mockNameStrategy.getStatementName(mockContext)).thenReturn(statementName);
+ when(mockRegistry.timer(statementName)).thenReturn(mockTimer);
+
+ when(mockContext.getElapsedTime(ChronoUnit.NANOS)).thenReturn(fakeElapsed);
+
+ logger.logException(mockContext, new SQLException());
+
+ verify(mockTimer).update(fakeElapsed, TimeUnit.NANOSECONDS);
+ }
+}
diff --git a/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/AbstractStrategyTest.java b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/AbstractStrategyTest.java
new file mode 100644
index 0000000..c6fdcde
--- /dev/null
+++ b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/AbstractStrategyTest.java
@@ -0,0 +1,23 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import com.codahale.metrics.MetricRegistry;
+import org.jdbi.v3.core.statement.StatementContext;
+import org.junit.Before;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AbstractStrategyTest {
+
+ MetricRegistry registry = new MetricRegistry();
+ StatementContext ctx = mock(StatementContext.class);
+
+ @Before
+ public void setUp() throws Exception {
+ when(ctx.getRawSql()).thenReturn("SELECT 1");
+ }
+
+ long getTimerMaxValue(String name) {
+ return registry.timer(name).getSnapshot().getMax();
+ }
+}
diff --git a/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/BasicSqlNameStrategyTest.java b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/BasicSqlNameStrategyTest.java
new file mode 100644
index 0000000..956b964
--- /dev/null
+++ b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/BasicSqlNameStrategyTest.java
@@ -0,0 +1,21 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import org.jdbi.v3.core.extension.ExtensionMethod;
+import org.junit.Test;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+public class BasicSqlNameStrategyTest extends AbstractStrategyTest {
+
+ private BasicSqlNameStrategy basicSqlNameStrategy = new BasicSqlNameStrategy();
+
+ @Test
+ public void producesMethodNameAsMetric() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(new ExtensionMethod(getClass(), getClass().getMethod("producesMethodNameAsMetric")));
+ String name = basicSqlNameStrategy.getStatementName(ctx);
+ assertThat(name).isEqualTo(name(getClass(), "producesMethodNameAsMetric"));
+ }
+
+}
diff --git a/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/NaiveNameStrategyTest.java b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/NaiveNameStrategyTest.java
new file mode 100644
index 0000000..076423a
--- /dev/null
+++ b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/NaiveNameStrategyTest.java
@@ -0,0 +1,17 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NaiveNameStrategyTest extends AbstractStrategyTest {
+
+ private NaiveNameStrategy naiveNameStrategy = new NaiveNameStrategy();
+
+ @Test
+ public void producesSqlRawMetrics() throws Exception {
+ String name = naiveNameStrategy.getStatementName(ctx);
+ assertThat(name).isEqualToIgnoringCase("SELECT 1");
+ }
+
+}
diff --git a/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/SmartNameStrategyTest.java b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/SmartNameStrategyTest.java
new file mode 100644
index 0000000..4bee835
--- /dev/null
+++ b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/SmartNameStrategyTest.java
@@ -0,0 +1,69 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import com.codahale.metrics.jdbi3.InstrumentedTimingCollector;
+import org.jdbi.v3.core.extension.ExtensionMethod;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+public class SmartNameStrategyTest extends AbstractStrategyTest {
+
+ private StatementNameStrategy smartNameStrategy = new SmartNameStrategy();
+ private InstrumentedTimingCollector collector;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ collector = new InstrumentedTimingCollector(registry, smartNameStrategy);
+ }
+
+ @Test
+ public void updatesTimerForSqlObjects() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(
+ new ExtensionMethod(getClass(), getClass().getMethod("updatesTimerForSqlObjects")));
+
+ collector.collect(TimeUnit.SECONDS.toNanos(1), ctx);
+
+ String name = smartNameStrategy.getStatementName(ctx);
+ assertThat(name).isEqualTo(name(getClass(), "updatesTimerForSqlObjects"));
+ assertThat(getTimerMaxValue(name)).isEqualTo(1000000000);
+ }
+
+ @Test
+ public void updatesTimerForRawSql() throws Exception {
+ collector.collect(TimeUnit.SECONDS.toNanos(2), ctx);
+
+ String name = smartNameStrategy.getStatementName(ctx);
+ assertThat(name).isEqualTo(name("sql", "raw"));
+ assertThat(getTimerMaxValue(name)).isEqualTo(2000000000);
+ }
+
+ @Test
+ public void updatesTimerForNoRawSql() throws Exception {
+ reset(ctx);
+
+ collector.collect(TimeUnit.SECONDS.toNanos(2), ctx);
+
+ String name = smartNameStrategy.getStatementName(ctx);
+ assertThat(name).isEqualTo(name("sql", "empty"));
+ assertThat(getTimerMaxValue(name)).isEqualTo(2000000000);
+ }
+
+ @Test
+ public void updatesTimerForContextClass() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(new ExtensionMethod(getClass(),
+ getClass().getMethod("updatesTimerForContextClass")));
+ collector.collect(TimeUnit.SECONDS.toNanos(3), ctx);
+
+ String name = smartNameStrategy.getStatementName(ctx);
+ assertThat(name).isEqualTo(name(getClass(), "updatesTimerForContextClass"));
+ assertThat(getTimerMaxValue(name)).isEqualTo(3000000000L);
+ }
+}
diff --git a/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/TimedAnnotationNameStrategyTest.java b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/TimedAnnotationNameStrategyTest.java
new file mode 100644
index 0000000..4852d3f
--- /dev/null
+++ b/metrics-jdbi3/src/test/java/com/codahale/metrics/jdbi3/strategies/TimedAnnotationNameStrategyTest.java
@@ -0,0 +1,88 @@
+package com.codahale.metrics.jdbi3.strategies;
+
+import com.codahale.metrics.annotation.Timed;
+import org.jdbi.v3.core.extension.ExtensionMethod;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TimedAnnotationNameStrategyTest extends AbstractStrategyTest {
+
+ private TimedAnnotationNameStrategy timedAnnotationNameStrategy = new TimedAnnotationNameStrategy();
+
+ public interface Foo {
+
+ @Timed
+ void update();
+
+ @Timed(name = "custom-update")
+ void customUpdate();
+
+ @Timed(name = "absolute-update", absolute = true)
+ void absoluteUpdate();
+ }
+
+
+ @Timed
+ public interface Bar {
+
+ void update();
+ }
+
+ @Timed(name = "custom-bar")
+ public interface CustomBar {
+
+ @Timed(name = "find-by-id")
+ int find(String name);
+ }
+
+ public interface Dummy {
+
+ void show();
+ }
+
+ @Test
+ public void testAnnotationOnMethod() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(new ExtensionMethod(Foo.class, Foo.class.getMethod("update")));
+ assertThat(timedAnnotationNameStrategy.getStatementName(ctx))
+ .isEqualTo("com.codahale.metrics.jdbi3.strategies.TimedAnnotationNameStrategyTest$Foo.update");
+ }
+
+ @Test
+ public void testAnnotationOnMethodWithCustomName() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(new ExtensionMethod(Foo.class, Foo.class.getMethod("customUpdate")));
+ assertThat(timedAnnotationNameStrategy.getStatementName(ctx))
+ .isEqualTo("com.codahale.metrics.jdbi3.strategies.TimedAnnotationNameStrategyTest$Foo.custom-update");
+ }
+
+ @Test
+ public void testAnnotationOnMethodWithCustomAbsoluteName() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(new ExtensionMethod(Foo.class, Foo.class.getMethod("absoluteUpdate")));
+ assertThat(timedAnnotationNameStrategy.getStatementName(ctx)).isEqualTo("absolute-update");
+ }
+
+ @Test
+ public void testAnnotationOnClass() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(new ExtensionMethod(Bar.class, Bar.class.getMethod("update")));
+ assertThat(timedAnnotationNameStrategy.getStatementName(ctx))
+ .isEqualTo("com.codahale.metrics.jdbi3.strategies.TimedAnnotationNameStrategyTest$Bar.update");
+ }
+
+ @Test
+ public void testAnnotationOnMethodAndClassWithCustomNames() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(new ExtensionMethod(CustomBar.class, CustomBar.class.getMethod("find", String.class)));
+ assertThat(timedAnnotationNameStrategy.getStatementName(ctx)).isEqualTo("custom-bar.find-by-id");
+ }
+
+ @Test
+ public void testNoAnnotations() throws Exception {
+ when(ctx.getExtensionMethod()).thenReturn(new ExtensionMethod(Dummy.class, Dummy.class.getMethod("show")));
+ assertThat(timedAnnotationNameStrategy.getStatementName(ctx)).isNull();
+ }
+
+ @Test
+ public void testNoMethod() {
+ assertThat(timedAnnotationNameStrategy.getStatementName(ctx)).isNull();
+ }
+}
\ No newline at end of file
diff --git a/metrics-jersey/pom.xml b/metrics-jersey/pom.xml
deleted file mode 100644
index 6d02b3f..0000000
--- a/metrics-jersey/pom.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?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.6</version>
- </parent>
-
- <artifactId>metrics-jersey</artifactId>
- <name>Metrics Integration for Jersey 1.x</name>
- <packaging>bundle</packaging>
- <description>
- A set of class providing Metrics integration for Jersey, the reference JAX-RS
- implementation. This module is for the old Jersey 1.x
- </description>
-
- <properties>
- <jersey.version>1.18.1</jersey.version>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-annotation</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>com.sun.jersey</groupId>
- <artifactId>jersey-server</artifactId>
- <version>${jersey.version}</version>
- </dependency>
- <dependency>
- <groupId>com.sun.jersey.jersey-test-framework</groupId>
- <artifactId>jersey-test-framework-inmemory</artifactId>
- <version>${jersey.version}</version>
- <scope>test</scope>
- <exclusions>
- <exclusion>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- </dependencies>
-</project>
diff --git a/metrics-jersey/src/main/java/com/codahale/metrics/jersey/InstrumentedResourceMethodDispatchAdapter.java b/metrics-jersey/src/main/java/com/codahale/metrics/jersey/InstrumentedResourceMethodDispatchAdapter.java
deleted file mode 100644
index 931fc96..0000000
--- a/metrics-jersey/src/main/java/com/codahale/metrics/jersey/InstrumentedResourceMethodDispatchAdapter.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.codahale.metrics.jersey;
-
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.SharedMetricRegistries;
-import com.sun.jersey.spi.container.ResourceMethodDispatchAdapter;
-import com.sun.jersey.spi.container.ResourceMethodDispatchProvider;
-
-import javax.ws.rs.ext.Provider;
-
-/**
- * A provider that wraps a {@link ResourceMethodDispatchProvider} in an
- * {@link InstrumentedResourceMethodDispatchProvider}
- */
-@Provider
-public class InstrumentedResourceMethodDispatchAdapter implements ResourceMethodDispatchAdapter {
- private final MetricRegistry registry;
-
- /**
- * Construct a resource method dispatch adapter using the given metrics registry name.
- *
- * @param registryName the name of a shared metric registry
- */
- public InstrumentedResourceMethodDispatchAdapter(String registryName) {
- this(SharedMetricRegistries.getOrCreate(registryName));
- }
-
- /**
- * Construct a resource method dispatch adapter using the given metrics registry.
- * <p/>
- * When using this constructor, the {@link InstrumentedResourceMethodDispatchAdapter}
- * should be added to a Jersey {@code ResourceConfig} as a singleton.
- *
- * @param registry a {@link MetricRegistry}
- */
- public InstrumentedResourceMethodDispatchAdapter(MetricRegistry registry) {
- this.registry = registry;
- }
-
-
- @Override
- public ResourceMethodDispatchProvider adapt(ResourceMethodDispatchProvider provider) {
- return new InstrumentedResourceMethodDispatchProvider(provider, registry);
- }
-}
diff --git a/metrics-jersey/src/main/java/com/codahale/metrics/jersey/InstrumentedResourceMethodDispatchProvider.java b/metrics-jersey/src/main/java/com/codahale/metrics/jersey/InstrumentedResourceMethodDispatchProvider.java
deleted file mode 100644
index 3530a61..0000000
--- a/metrics-jersey/src/main/java/com/codahale/metrics/jersey/InstrumentedResourceMethodDispatchProvider.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package com.codahale.metrics.jersey;
-
-import com.sun.jersey.api.core.HttpContext;
-import com.sun.jersey.api.model.AbstractResourceMethod;
-import com.sun.jersey.spi.container.ResourceMethodDispatchProvider;
-import com.sun.jersey.spi.dispatch.RequestDispatcher;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.Timer;
-import com.codahale.metrics.annotation.ExceptionMetered;
-import com.codahale.metrics.annotation.Metered;
-import com.codahale.metrics.annotation.Timed;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-class InstrumentedResourceMethodDispatchProvider implements ResourceMethodDispatchProvider {
- private static class TimedRequestDispatcher implements RequestDispatcher {
- private final RequestDispatcher underlying;
- private final Timer timer;
-
- private TimedRequestDispatcher(RequestDispatcher underlying, Timer timer) {
- this.underlying = underlying;
- this.timer = timer;
- }
-
- @Override
- public void dispatch(Object resource, HttpContext httpContext) {
- final Timer.Context context = timer.time();
- try {
- underlying.dispatch(resource, httpContext);
- } finally {
- context.stop();
- }
- }
- }
-
- private static class MeteredRequestDispatcher implements RequestDispatcher {
- private final RequestDispatcher underlying;
- private final Meter meter;
-
- private MeteredRequestDispatcher(RequestDispatcher underlying, Meter meter) {
- this.underlying = underlying;
- this.meter = meter;
- }
-
- @Override
- public void dispatch(Object resource, HttpContext httpContext) {
- meter.mark();
- underlying.dispatch(resource, httpContext);
- }
- }
-
- private static class ExceptionMeteredRequestDispatcher implements RequestDispatcher {
- private final RequestDispatcher underlying;
- private final Meter meter;
- private final Class<? extends Throwable> exceptionClass;
-
- private ExceptionMeteredRequestDispatcher(RequestDispatcher underlying,
- Meter meter,
- Class<? extends Throwable> exceptionClass) {
- this.underlying = underlying;
- this.meter = meter;
- this.exceptionClass = exceptionClass;
- }
-
- @Override
- public void dispatch(Object resource, HttpContext httpContext) {
- try {
- underlying.dispatch(resource, httpContext);
- } catch (Exception e) {
- if (exceptionClass.isAssignableFrom(e.getClass()) ||
- (e.getCause() != null && exceptionClass.isAssignableFrom(e.getCause().getClass()))) {
- meter.mark();
- }
- InstrumentedResourceMethodDispatchProvider.<RuntimeException>throwUnchecked(e);
- }
- }
- }
-
- /*
- * A dirty hack to allow us to throw exceptions of any type without bringing down the unsafe
- * thunder.
- */
- @SuppressWarnings("unchecked")
- private static <T extends Exception> void throwUnchecked(Throwable e) throws T {
- throw (T) e;
- }
-
- private final ResourceMethodDispatchProvider provider;
- private final MetricRegistry registry;
-
- public InstrumentedResourceMethodDispatchProvider(ResourceMethodDispatchProvider provider,
- MetricRegistry registry) {
- this.provider = provider;
- this.registry = registry;
- }
-
- @Override
- public RequestDispatcher create(AbstractResourceMethod method) {
- RequestDispatcher dispatcher = provider.create(method);
- if (dispatcher == null) {
- return null;
- }
-
- if (method.getMethod().isAnnotationPresent(Timed.class)) {
- final Timed annotation = method.getMethod().getAnnotation(Timed.class);
- final String name = chooseName(annotation.name(), annotation.absolute(), method);
- final Timer timer = registry.timer(name);
- dispatcher = new TimedRequestDispatcher(dispatcher, timer);
- }
-
- if (method.getMethod().isAnnotationPresent(Metered.class)) {
- final Metered annotation = method.getMethod().getAnnotation(Metered.class);
- final String name = chooseName(annotation.name(), annotation.absolute(), method);
- final Meter meter = registry.meter(name);
- dispatcher = new MeteredRequestDispatcher(dispatcher, meter);
- }
-
- if (method.getMethod().isAnnotationPresent(ExceptionMetered.class)) {
- final ExceptionMetered annotation = method.getMethod()
- .getAnnotation(ExceptionMetered.class);
- final String name = chooseName(annotation.name(),
- annotation.absolute(),
- method,
- ExceptionMetered.DEFAULT_NAME_SUFFIX);
- final Meter meter = registry.meter(name);
- dispatcher = new ExceptionMeteredRequestDispatcher(dispatcher,
- meter,
- annotation.cause());
- }
-
- return dispatcher;
- }
-
- private String chooseName(String explicitName, boolean absolute, AbstractResourceMethod method, String... suffixes) {
- if (explicitName != null && !explicitName.isEmpty()) {
- if (absolute) {
- return explicitName;
- }
- return name(method.getDeclaringResource().getResourceClass(), explicitName);
- }
- return name(name(method.getDeclaringResource().getResourceClass(),
- method.getMethod().getName()),
- suffixes);
- }
-}
diff --git a/metrics-jersey/src/test/java/com/codahale/metrics/jersey/SingletonMetricsJerseyTest.java b/metrics-jersey/src/test/java/com/codahale/metrics/jersey/SingletonMetricsJerseyTest.java
deleted file mode 100644
index 4ff1bca..0000000
--- a/metrics-jersey/src/test/java/com/codahale/metrics/jersey/SingletonMetricsJerseyTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.codahale.metrics.jersey;
-
-import com.sun.jersey.api.container.MappableContainerException;
-import com.sun.jersey.api.core.DefaultResourceConfig;
-import com.sun.jersey.test.framework.AppDescriptor;
-import com.sun.jersey.test.framework.JerseyTest;
-import com.sun.jersey.test.framework.LowLevelAppDescriptor;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.Timer;
-import com.codahale.metrics.jersey.resources.InstrumentedResource;
-import org.junit.Test;
-
-import java.io.IOException;
-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;
-import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
-
-/**
- * Tests importing {@link InstrumentedResourceMethodDispatchAdapter} as a singleton
- * in a Jersey {@link com.sun.jersey.api.core.ResourceConfig}
- */
-public class SingletonMetricsJerseyTest extends JerseyTest {
- static {
- Logger.getLogger("com.sun.jersey").setLevel(Level.OFF);
- }
-
- private MetricRegistry registry;
-
- @Override
- protected AppDescriptor configure() {
- this.registry = new MetricRegistry();
-
- final DefaultResourceConfig config = new DefaultResourceConfig();
- config.getSingletons().add(new InstrumentedResourceMethodDispatchAdapter(registry));
- config.getClasses().add(InstrumentedResource.class);
-
- return new LowLevelAppDescriptor.Builder(config).build();
- }
-
- @Test
- public void timedMethodsAreTimed() {
- assertThat(resource().path("timed").get(String.class))
- .isEqualTo("yay");
-
- final Timer timer = registry.timer(name(InstrumentedResource.class, "timed"));
-
- assertThat(timer.getCount())
- .isEqualTo(1);
- }
-
- @Test
- public void meteredMethodsAreMetered() {
- assertThat(resource().path("metered").get(String.class))
- .isEqualTo("woo");
-
- 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,
- "exceptionMetered",
- "exceptions"));
-
- assertThat(resource().path("exception-metered").get(String.class))
- .isEqualTo("fuh");
-
- assertThat(meter.getCount())
- .isZero();
-
- try {
- resource().path("exception-metered").queryParam("splode", "true").get(String.class);
- failBecauseExceptionWasNotThrown(MappableContainerException.class);
- } catch (MappableContainerException e) {
- assertThat(e.getCause())
- .isInstanceOf(IOException.class);
- }
-
- assertThat(meter.getCount())
- .isEqualTo(1);
- }
-}
diff --git a/metrics-jersey/src/test/java/com/codahale/metrics/jersey/resources/InstrumentedResource.java b/metrics-jersey/src/test/java/com/codahale/metrics/jersey/resources/InstrumentedResource.java
deleted file mode 100644
index d2cf346..0000000
--- a/metrics-jersey/src/test/java/com/codahale/metrics/jersey/resources/InstrumentedResource.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.codahale.metrics.jersey.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;
-
-@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";
- }
-
- @GET
- @ExceptionMetered(cause = IOException.class)
- @Path("/exception-metered")
- public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
- if (splode) {
- throw new IOException("AUGH");
- }
- return "fuh";
- }
-}
diff --git a/metrics-jersey2/pom.xml b/metrics-jersey2/pom.xml
index 8a7af11..c38ab74 100644
--- a/metrics-jersey2/pom.xml
+++ b/metrics-jersey2/pom.xml
@@ -3,62 +3,103 @@
<modelVersion>4.0.0</modelVersion>
<parent>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-parent</artifactId>
- <version>3.2.6</version>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-parent</artifactId>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-jersey2</artifactId>
<name>Metrics Integration for Jersey 2.x</name>
<packaging>bundle</packaging>
<description>
- A set of class providing Metrics integration for Jersey, the reference JAX-RS
- implementation.
+ A set of class providing Metrics integration for Jersey, the reference JAX-RS
+ implementation.
</description>
<properties>
- <jersey.version>2.11</jersey.version>
+ <javaModuleName>com.codahale.metrics.jersey2</javaModuleName>
+ <jersey.version>2.41</jersey.version>
</properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey</groupId>
+ <artifactId>jersey-bom</artifactId>
+ <version>${jersey.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.annotation</groupId>
+ <artifactId>jakarta.annotation-api</artifactId>
+ <version>1.3.5</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
- <dependency>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-annotation</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.glassfish.jersey.core</groupId>
- <artifactId>jersey-server</artifactId>
- <version>${jersey.version}</version>
- </dependency>
- <dependency>
- <groupId>
- org.glassfish.jersey.test-framework.providers
- </groupId>
- <artifactId>
- jersey-test-framework-provider-inmemory
- </artifactId>
- <version>${jersey.version}</version>
- <scope>test</scope>
- </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.ws.rs</groupId>
+ <artifactId>jakarta.ws.rs-api</artifactId>
+ <version>2.1.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-inmemory</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.1</version>
- <configuration>
- <source>1.7</source>
- <target>1.7</target>
- </configuration>
- </plugin>
- </plugins>
- </build>
</project>
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 7454953..7697e11 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
@@ -1,11 +1,17 @@
package com.codahale.metrics.jersey2;
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Reservoir;
import com.codahale.metrics.Timer;
import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Metered;
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
import com.codahale.metrics.annotation.Timed;
+import org.glassfish.jersey.server.ContainerResponse;
import org.glassfish.jersey.server.model.ModelProcessor;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
@@ -19,40 +25,87 @@ 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.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
/**
* An application event listener that listens for Jersey application initialization to
* be finished, then creates a map of resource method that have metrics annotations.
- * <p/>
+ * <p>
* Finally, it listens for method start events, and returns a {@link RequestEventListener}
* that updates the relevant metric for suitably annotated methods when it gets the
* request events indicating that the method is about to be invoked, or just got done
* being invoked.
*/
-
@Provider
public class InstrumentedResourceMethodApplicationListener implements ApplicationEventListener, ModelProcessor {
+ private static final String[] REQUEST_FILTERING = {"request", "filtering"};
+ private static final String[] RESPONSE_FILTERING = {"response", "filtering"};
+ private static final String TOTAL = "total";
+
private final MetricRegistry metrics;
- private ConcurrentMap<Method, Timer> timers = new ConcurrentHashMap<>();
- private ConcurrentMap<Method, Meter> meters = new ConcurrentHashMap<>();
- private ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters = new ConcurrentHashMap<>();
+ private final ConcurrentMap<EventTypeAndMethod, Timer> timers = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, Meter> meters = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, ResponseMeterMetric> responseMeters = new ConcurrentHashMap<>();
+
+ private final Clock clock;
+ private final boolean trackFilters;
+ private final Supplier<Reservoir> reservoirSupplier;
/**
* Construct an application event listener using the given metrics registry.
- * <p/>
- * <p/>
+ * <p>
* When using this constructor, the {@link InstrumentedResourceMethodApplicationListener}
* should be added to a Jersey {@code ResourceConfig} as a singleton.
*
* @param metrics a {@link MetricRegistry}
*/
public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics) {
+ this(metrics, Clock.defaultClock(), false);
+ }
+
+ /**
+ * Constructs a custom application listener.
+ *
+ * @param metrics the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ */
+ public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics, final Clock clock,
+ final boolean trackFilters) {
+ this(metrics, clock, trackFilters, ExponentiallyDecayingReservoir::new);
+ }
+
+ /**
+ * Constructs a custom application listener.
+ *
+ * @param metrics the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics, final Clock clock,
+ final boolean trackFilters,
+ final Supplier<Reservoir> reservoirSupplier) {
this.metrics = metrics;
+ this.clock = clock;
+ this.trackFilters = trackFilters;
+ this.reservoirSupplier = reservoirSupplier;
}
/**
@@ -74,28 +127,124 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
}
}
+ /**
+ * A private class to maintain the metrics for a method annotated with the
+ * {@link ResponseMetered} annotation, which needs to maintain meters for
+ * different response codes
+ */
+ private static class ResponseMeterMetric {
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+ private final List<Meter> meters;
+ private final Map<Integer, Meter> responseCodeMeters;
+ private final MetricRegistry metricRegistry;
+ private final String metricName;
+ private final ResponseMeteredLevel level;
+
+ public ResponseMeterMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final ResponseMetered responseMetered) {
+ this.metricName = chooseName(responseMetered.name(), responseMetered.absolute(), method);
+ this.level = responseMetered.level();
+ this.meters = COARSE_METER_LEVELS.contains(level) ?
+ Collections.unmodifiableList(Arrays.asList(
+ registry.meter(name(metricName, "1xx-responses")), // 1xx
+ registry.meter(name(metricName, "2xx-responses")), // 2xx
+ registry.meter(name(metricName, "3xx-responses")), // 3xx
+ registry.meter(name(metricName, "4xx-responses")), // 4xx
+ registry.meter(name(metricName, "5xx-responses")) // 5xx
+ )) : Collections.emptyList();
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(level) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+ this.metricRegistry = registry;
+ }
+
+ public void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(level)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(level)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ meters.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(metricName, String.format("%d-responses", sc))));
+ }
+ }
+
private static class TimerRequestEventListener implements RequestEventListener {
- private final ConcurrentMap<Method, Timer> timers;
- private Timer.Context context = null;
- public TimerRequestEventListener(final ConcurrentMap<Method, Timer> timers) {
+ private final ConcurrentMap<EventTypeAndMethod, Timer> timers;
+ private final Clock clock;
+ private final long start;
+ private Timer.Context resourceMethodStartContext;
+ private Timer.Context requestMatchedContext;
+ private Timer.Context responseFiltersStartContext;
+
+ public TimerRequestEventListener(final ConcurrentMap<EventTypeAndMethod, Timer> timers, final Clock clock) {
this.timers = timers;
+ this.clock = clock;
+ start = clock.getTick();
}
@Override
public void onEvent(RequestEvent event) {
- if (event.getType() == RequestEvent.Type.RESOURCE_METHOD_START) {
- final Timer timer = this.timers.get(event.getUriInfo()
- .getMatchedResourceMethod().getInvocable().getDefinitionMethod());
- if (timer != null) {
- this.context = timer.time();
- }
- } else if (event.getType() == RequestEvent.Type.RESOURCE_METHOD_FINISHED) {
- if (this.context != null) {
- this.context.close();
- }
+ switch (event.getType()) {
+ case RESOURCE_METHOD_START:
+ resourceMethodStartContext = context(event);
+ break;
+ case REQUEST_MATCHED:
+ requestMatchedContext = context(event);
+ break;
+ case RESP_FILTERS_START:
+ responseFiltersStartContext = context(event);
+ break;
+ case RESOURCE_METHOD_FINISHED:
+ if (resourceMethodStartContext != null) {
+ resourceMethodStartContext.close();
+ }
+ break;
+ case REQUEST_FILTERED:
+ if (requestMatchedContext != null) {
+ requestMatchedContext.close();
+ }
+ break;
+ case RESP_FILTERS_FINISHED:
+ if (responseFiltersStartContext != null) {
+ responseFiltersStartContext.close();
+ }
+ break;
+ case FINISHED:
+ if (requestMatchedContext != null && responseFiltersStartContext != null) {
+ final Timer timer = timer(event);
+ if (timer != null) {
+ timer.update(clock.getTick() - start, TimeUnit.NANOSECONDS);
+ }
+ }
+ break;
+ default:
+ break;
}
}
+
+ private Timer timer(RequestEvent event) {
+ final ResourceMethod resourceMethod = event.getUriInfo().getMatchedResourceMethod();
+ if (resourceMethod == null) {
+ return null;
+ }
+ return timers.get(new EventTypeAndMethod(event.getType(), resourceMethod.getInvocable().getDefinitionMethod()));
+ }
+
+ private Timer.Context context(RequestEvent event) {
+ final Timer timer = timer(event);
+ return timer != null ? timer.time() : null;
+ }
}
private static class MeterRequestEventListener implements RequestEventListener {
@@ -108,8 +257,7 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
@Override
public void onEvent(RequestEvent event) {
if (event.getType() == RequestEvent.Type.RESOURCE_METHOD_START) {
- final Meter meter = this.meters.get(event.getUriInfo()
- .getMatchedResourceMethod().getInvocable().getDefinitionMethod());
+ final Meter meter = this.meters.get(event.getUriInfo().getMatchedResourceMethod().getInvocable().getDefinitionMethod());
if (meter != null) {
meter.mark();
}
@@ -142,6 +290,32 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
}
}
+ private static class ResponseMeterRequestEventListener implements RequestEventListener {
+ private final ConcurrentMap<Method, ResponseMeterMetric> responseMeters;
+
+ public ResponseMeterRequestEventListener(final ConcurrentMap<Method, ResponseMeterMetric> responseMeters) {
+ this.responseMeters = responseMeters;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ if (event.getType() == RequestEvent.Type.FINISHED) {
+ final ResourceMethod method = event.getUriInfo().getMatchedResourceMethod();
+ final ResponseMeterMetric metric = (method != null) ?
+ this.responseMeters.get(method.getInvocable().getDefinitionMethod()) : null;
+
+ if (metric != null) {
+ ContainerResponse containerResponse = event.getContainerResponse();
+ if (containerResponse == null && event.getException() != null) {
+ metric.mark(500);
+ } else if (containerResponse != null) {
+ metric.mark(containerResponse.getStatus());
+ }
+ }
+ }
+ }
+ }
+
private static class ChainedRequestEventListener implements RequestEventListener {
private final RequestEventListener[] listeners;
@@ -181,11 +355,13 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
final Timed classLevelTimed = getClassLevelAnnotation(resource, Timed.class);
final Metered classLevelMetered = getClassLevelAnnotation(resource, Metered.class);
final ExceptionMetered classLevelExceptionMetered = getClassLevelAnnotation(resource, ExceptionMetered.class);
+ final ResponseMetered classLevelResponseMetered = getClassLevelAnnotation(resource, ResponseMetered.class);
for (final ResourceMethod method : resource.getAllMethods()) {
registerTimedAnnotations(method, classLevelTimed);
registerMeteredAnnotations(method, classLevelMetered);
registerExceptionMeteredAnnotations(method, classLevelExceptionMetered);
+ registerResponseMeteredAnnotations(method, classLevelResponseMetered);
}
for (final Resource childResource : resource.getChildResources()) {
@@ -193,23 +369,25 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
final Timed classLevelTimedChild = getClassLevelAnnotation(childResource, Timed.class);
final Metered classLevelMeteredChild = getClassLevelAnnotation(childResource, Metered.class);
final ExceptionMetered classLevelExceptionMeteredChild = getClassLevelAnnotation(childResource, ExceptionMetered.class);
+ final ResponseMetered classLevelResponseMeteredChild = getClassLevelAnnotation(childResource, ResponseMetered.class);
for (final ResourceMethod method : childResource.getAllMethods()) {
registerTimedAnnotations(method, classLevelTimedChild);
registerMeteredAnnotations(method, classLevelMeteredChild);
registerExceptionMeteredAnnotations(method, classLevelExceptionMeteredChild);
+ registerResponseMeteredAnnotations(method, classLevelResponseMeteredChild);
}
}
}
-
}
@Override
public RequestEventListener onRequest(final RequestEvent event) {
final RequestEventListener listener = new ChainedRequestEventListener(
- new TimerRequestEventListener(timers),
+ new TimerRequestEventListener(timers, clock),
new MeterRequestEventListener(meters),
- new ExceptionMeterRequestEventListener(exceptionMeters));
+ new ExceptionMeterRequestEventListener(exceptionMeters),
+ new ResponseMeterRequestEventListener(responseMeters));
return listener;
}
@@ -230,14 +408,22 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
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));
+ registerTimers(method, definitionMethod, classLevelTimed);
return;
}
final Timed annotation = definitionMethod.getAnnotation(Timed.class);
-
if (annotation != null) {
- timers.putIfAbsent(definitionMethod, timerMetric(this.metrics, method, annotation));
+ registerTimers(method, definitionMethod, annotation);
+ }
+ }
+
+ private void registerTimers(ResourceMethod method, Method definitionMethod, Timed annotation) {
+ timers.putIfAbsent(EventTypeAndMethod.requestMethodStart(definitionMethod), timerMetric(metrics, method, annotation));
+ if (trackFilters) {
+ timers.putIfAbsent(EventTypeAndMethod.requestMatched(definitionMethod), timerMetric(metrics, method, annotation, REQUEST_FILTERING));
+ timers.putIfAbsent(EventTypeAndMethod.respFiltersStart(definitionMethod), timerMetric(metrics, method, annotation, RESPONSE_FILTERING));
+ timers.putIfAbsent(EventTypeAndMethod.finished(definitionMethod), timerMetric(metrics, method, annotation, TOTAL));
}
}
@@ -269,30 +455,95 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
}
}
- private static Timer timerMetric(final MetricRegistry registry,
- final ResourceMethod method,
- final Timed timed) {
- final String name = chooseName(timed.name(), timed.absolute(), method);
- return registry.timer(name);
+ private void registerResponseMeteredAnnotations(final ResourceMethod method, final ResponseMetered classLevelResponseMetered) {
+ final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+
+ if (classLevelResponseMetered != null) {
+ responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, classLevelResponseMetered));
+ return;
+ }
+ final ResponseMetered annotation = definitionMethod.getAnnotation(ResponseMetered.class);
+
+ if (annotation != null) {
+ responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, annotation));
+ }
}
- private static Meter meterMetric(final MetricRegistry registry,
- final ResourceMethod method,
- final Metered metered) {
+ private Timer timerMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final Timed timed,
+ final String... suffixes) {
+ final String name = chooseName(timed.name(), timed.absolute(), method, suffixes);
+ return registry.timer(name, () -> new Timer(reservoirSupplier.get(), clock));
+ }
+
+ private Meter meterMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final Metered metered) {
final String name = chooseName(metered.name(), metered.absolute(), method);
- return registry.meter(name);
+ return registry.meter(name, () -> new Meter(clock));
}
- protected static String chooseName(final String explicitName, final boolean absolute, final ResourceMethod method, final String... suffixes) {
+ protected static String chooseName(final String explicitName, final boolean absolute, final ResourceMethod method,
+ final String... suffixes) {
+ final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+ final String metricName;
if (explicitName != null && !explicitName.isEmpty()) {
- if (absolute) {
- return explicitName;
+ metricName = absolute ? explicitName : name(definitionMethod.getDeclaringClass(), explicitName);
+ } else {
+ metricName = name(definitionMethod.getDeclaringClass(), definitionMethod.getName());
+ }
+ return name(metricName, suffixes);
+ }
+
+ private static class EventTypeAndMethod {
+
+ private final RequestEvent.Type type;
+ private final Method method;
+
+ private EventTypeAndMethod(RequestEvent.Type type, Method method) {
+ this.type = type;
+ this.method = method;
+ }
+
+ static EventTypeAndMethod requestMethodStart(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.RESOURCE_METHOD_START, method);
+ }
+
+ static EventTypeAndMethod requestMatched(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.REQUEST_MATCHED, method);
+ }
+
+ static EventTypeAndMethod respFiltersStart(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.RESP_FILTERS_START, method);
+ }
+
+ static EventTypeAndMethod finished(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.FINISHED, method);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
}
- return name(method.getInvocable().getDefinitionMethod().getDeclaringClass(), explicitName);
+
+ EventTypeAndMethod that = (EventTypeAndMethod) o;
+
+ if (type != that.type) {
+ return false;
+ }
+ return method.equals(that.method);
}
- return name(name(method.getInvocable().getDefinitionMethod().getDeclaringClass(),
- method.getInvocable().getDefinitionMethod().getName()),
- suffixes);
+ @Override
+ public int hashCode() {
+ int result = type.hashCode();
+ result = 31 * result + method.hashCode();
+ return result;
+ }
}
}
diff --git a/metrics-jersey2/src/main/java/com/codahale/metrics/jersey2/MetricsFeature.java b/metrics-jersey2/src/main/java/com/codahale/metrics/jersey2/MetricsFeature.java
index a97caf9..0e65b14 100644
--- a/metrics-jersey2/src/main/java/com/codahale/metrics/jersey2/MetricsFeature.java
+++ b/metrics-jersey2/src/main/java/com/codahale/metrics/jersey2/MetricsFeature.java
@@ -1,10 +1,14 @@
package com.codahale.metrics.jersey2;
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Reservoir;
import com.codahale.metrics.SharedMetricRegistries;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
+import java.util.function.Supplier;
/**
* A {@link Feature} which registers a {@link InstrumentedResourceMethodApplicationListener}
@@ -13,9 +17,53 @@ import javax.ws.rs.core.FeatureContext;
public class MetricsFeature implements Feature {
private final MetricRegistry registry;
+ private final Clock clock;
+ private final boolean trackFilters;
+ private final Supplier<Reservoir> reservoirSupplier;
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ */
public MetricsFeature(MetricRegistry registry) {
+ this(registry, Clock.defaultClock());
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public MetricsFeature(MetricRegistry registry, Supplier<Reservoir> reservoirSupplier) {
+ this(registry, Clock.defaultClock(), false, reservoirSupplier);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock) {
+ this(registry, clock, false);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock, boolean trackFilters) {
+ this(registry, clock, trackFilters, ExponentiallyDecayingReservoir::new);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock, boolean trackFilters, Supplier<Reservoir> reservoirSupplier) {
this.registry = registry;
+ this.clock = clock;
+ this.trackFilters = trackFilters;
+ this.reservoirSupplier = reservoirSupplier;
}
public MetricsFeature(String registryName) {
@@ -25,7 +73,7 @@ public class MetricsFeature implements Feature {
/**
* A call-back method called when the feature is to be enabled in a given
* runtime configuration scope.
- * <p/>
+ * <p>
* The responsibility of the feature is to properly update the supplied runtime configuration context
* and return {@code true} if the feature was successfully enabled or {@code false} otherwise.
* <p>
@@ -35,7 +83,7 @@ public class MetricsFeature implements Feature {
* {@link javax.ws.rs.core.Configuration#isEnabled(javax.ws.rs.core.Feature)} or
* {@link javax.ws.rs.core.Configuration#isEnabled(Class)} method
* would return {@code false}.
- * </p>
+ * <p>
*
* @param context configurable context in which the feature should be enabled.
* @return {@code true} if the feature was successfully enabled, {@code false}
@@ -43,7 +91,7 @@ public class MetricsFeature implements Feature {
*/
@Override
public boolean configure(FeatureContext context) {
- context.register(new InstrumentedResourceMethodApplicationListener(registry));
+ context.register(new InstrumentedResourceMethodApplicationListener(registry, clock, trackFilters, reservoirSupplier));
return true;
}
}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/CustomReservoirImplementationTest.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/CustomReservoirImplementationTest.java
new file mode 100644
index 0000000..77d6965
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/CustomReservoirImplementationTest.java
@@ -0,0 +1,44 @@
+package com.codahale.metrics.jersey2;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.UniformReservoir;
+import com.codahale.metrics.jersey2.resources.InstrumentedResourceTimedPerClass;
+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;
+
+public class CustomReservoirImplementationTest extends JerseyTest {
+ static {
+ Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
+ }
+
+ private MetricRegistry registry;
+
+ @Override
+ protected Application configure() {
+ this.registry = new MetricRegistry();
+
+ return new ResourceConfig()
+ .register(new MetricsFeature(this.registry, UniformReservoir::new))
+ .register(InstrumentedResourceTimedPerClass.class);
+ }
+
+ @Test
+ public void timerHistogramIsUsingCustomReservoirImplementation() {
+ assertThat(target("timedPerClass").request().get(String.class)).isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedResourceTimedPerClass.class, "timedPerClass"));
+ assertThat(timer)
+ .extracting("histogram")
+ .extracting("reservoir")
+ .isInstanceOf(UniformReservoir.class);
+ }
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonFilterMetricsJerseyTest.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonFilterMetricsJerseyTest.java
new file mode 100644
index 0000000..ee05e7c
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonFilterMetricsJerseyTest.java
@@ -0,0 +1,162 @@
+package com.codahale.metrics.jersey2;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.jersey2.resources.InstrumentedFilteredResource;
+import com.codahale.metrics.jersey2.resources.TestRequestFilter;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Before;
+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} with filter tracking
+ */
+public class SingletonFilterMetricsJerseyTest extends JerseyTest {
+ static {
+ Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
+ }
+
+ private MetricRegistry registry;
+
+ private TestClock testClock;
+
+ @Override
+ protected Application configure() {
+ registry = new MetricRegistry();
+ testClock = new TestClock();
+ ResourceConfig config = new ResourceConfig();
+ config = config.register(new MetricsFeature(this.registry, testClock, true));
+ config = config.register(new TestRequestFilter(testClock));
+ config = config.register(new InstrumentedFilteredResource(testClock));
+ return config;
+ }
+
+ @Before
+ public void resetClock() {
+ testClock.tick = 0;
+ }
+
+ @Test
+ public void timedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void explicitNamesAreTimed() {
+ assertThat(target("named")
+ .request()
+ .get(String.class))
+ .isEqualTo("fancy");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "fancyName"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void absoluteNamesAreTimed() {
+ assertThat(target("absolute")
+ .request()
+ .get(String.class))
+ .isEqualTo("absolute");
+
+ final Timer timer = registry.timer("absolutelyFancy");
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void requestFiltersOfTimedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "request", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void responseFiltersOfTimedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "response", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void totalTimeOfTimedMethodsIsTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "total"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(5);
+ }
+
+ @Test
+ public void requestFiltersOfNamedMethodsAreTimed() {
+ assertThat(target("named")
+ .request()
+ .get(String.class))
+ .isEqualTo("fancy");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "fancyName", "request", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void requestFiltersOfAbsoluteMethodsAreTimed() {
+ assertThat(target("absolute")
+ .request()
+ .get(String.class))
+ .isEqualTo("absolute");
+
+ final Timer timer = registry.timer(name("absolutelyFancy", "request", "filtering"));
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void subResourcesFromLocatorsRegisterMetrics() {
+ assertThat(target("subresource/timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.InstrumentedFilteredSubResource.class,
+ "timed"));
+ assertThat(timer.getCount()).isEqualTo(1);
+
+ }
+}
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 4d95099..bb11703 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
@@ -94,6 +94,76 @@ public class SingletonMetricsJerseyTest extends JerseyTest {
assertThat(meter.getCount()).isEqualTo(1);
}
+ @Test
+ public void responseMeteredMethodsAreMeteredWithCoarseLevel() {
+ final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredCoarse",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredCoarse",
+ "200-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(target("response-metered-coarse")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ assertThat(meter2xx.getCount()).isOne();
+ assertThat(meter200.getCount()).isZero();
+ }
+
+ @Test
+ public void responseMeteredMethodsAreMeteredWithDetailedLevel() {
+ final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "200-responses"));
+ final Meter meter201 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "201-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(meter201.getCount()).isZero();
+ assertThat(target("response-metered-detailed")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+ assertThat(target("response-metered-detailed")
+ .queryParam("status_code", 201)
+ .request()
+ .get().getStatus())
+ .isEqualTo(201);
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isOne();
+ assertThat(meter201.getCount()).isOne();
+ }
+
+ @Test
+ public void responseMeteredMethodsAreMeteredWithAllLevel() {
+ final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredAll",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredAll",
+ "200-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(target("response-metered-all")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ assertThat(meter2xx.getCount()).isOne();
+ assertThat(meter200.getCount()).isOne();
+ }
+
@Test
public void testResourceNotFound() {
final Response response = target().path("not-found").request().get();
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsResponseMeteredPerClassJerseyTest.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsResponseMeteredPerClassJerseyTest.java
new file mode 100644
index 0000000..ac27e0e
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsResponseMeteredPerClassJerseyTest.java
@@ -0,0 +1,146 @@
+package com.codahale.metrics.jersey2;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jersey2.exception.mapper.TestExceptionMapper;
+import com.codahale.metrics.jersey2.resources.InstrumentedResourceResponseMeteredPerClass;
+import com.codahale.metrics.jersey2.resources.InstrumentedSubResourceResponseMeteredPerClass;
+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;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
+ * in a Jersey {@link ResourceConfig}
+ */
+public class SingletonMetricsResponseMeteredPerClassJerseyTest 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(InstrumentedResourceResponseMeteredPerClass.class);
+ config = config.register(new TestExceptionMapper());
+
+ return config;
+ }
+
+ @Test
+ public void responseMetered2xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered2xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ final Meter meter2xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered2xxPerClass",
+ "2xx-responses"));
+
+ assertThat(meter2xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMetered4xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered4xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(400);
+ assertThat(target("responseMeteredBadRequestPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(400);
+
+ final Meter meter4xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered4xxPerClass",
+ "4xx-responses"));
+ final Meter meterException4xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredBadRequestPerClass",
+ "4xx-responses"));
+
+ assertThat(meter4xx.getCount()).isEqualTo(1);
+ assertThat(meterException4xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMetered5xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered5xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(500);
+
+ final Meter meter5xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered5xxPerClass",
+ "5xx-responses"));
+
+ assertThat(meter5xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMeteredMappedExceptionPerClassMethodsAreMetered() {
+ assertThat(target("responseMeteredTestExceptionPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(500);
+
+ final Meter meterTestException = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredTestExceptionPerClass",
+ "5xx-responses"));
+
+ assertThat(meterTestException.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMeteredUnmappedExceptionPerClassMethodsAreMetered() {
+ try {
+ target("responseMeteredRuntimeExceptionPerClass")
+ .request()
+ .get();
+ fail("expected RuntimeException");
+ } catch (Exception e) {
+ assertThat(e.getCause()).isInstanceOf(RuntimeException.class);
+ }
+
+ final Meter meterException5xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredRuntimeExceptionPerClass",
+ "5xx-responses"));
+
+ assertThat(meterException5xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void subresourcesFromLocatorsRegisterMetrics() {
+ final Meter meter2xx = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
+ "responseMeteredPerClass",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
+ "responseMeteredPerClass",
+ "200-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(target("subresource/responseMeteredPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ assertThat(meter2xx.getCount()).isOne();
+ assertThat(meter200.getCount()).isOne();
+ }
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/TestClock.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/TestClock.java
new file mode 100644
index 0000000..1f2cf9c
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/TestClock.java
@@ -0,0 +1,13 @@
+package com.codahale.metrics.jersey2;
+
+import com.codahale.metrics.Clock;
+
+public class TestClock extends Clock {
+
+ public long tick;
+
+ @Override
+ public long getTick() {
+ return tick;
+ }
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/exception/TestException.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/exception/TestException.java
new file mode 100644
index 0000000..2b0512a
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/exception/TestException.java
@@ -0,0 +1,9 @@
+package com.codahale.metrics.jersey2.exception;
+
+public class TestException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public TestException(String message) {
+ super(message);
+ }
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/exception/mapper/TestExceptionMapper.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/exception/mapper/TestExceptionMapper.java
new file mode 100644
index 0000000..296a054
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/exception/mapper/TestExceptionMapper.java
@@ -0,0 +1,15 @@
+package com.codahale.metrics.jersey2.exception.mapper;
+
+import com.codahale.metrics.jersey2.exception.TestException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+@Provider
+public class TestExceptionMapper implements ExceptionMapper<TestException> {
+ @Override
+ public Response toResponse(TestException exception) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedFilteredResource.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedFilteredResource.java
new file mode 100644
index 0000000..46a0be5
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedFilteredResource.java
@@ -0,0 +1,62 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import com.codahale.metrics.jersey2.TestClock;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedFilteredResource {
+
+ private final TestClock testClock;
+
+ public InstrumentedFilteredResource(TestClock testClock) {
+ this.testClock = testClock;
+ }
+
+ @GET
+ @Timed
+ @Path("/timed")
+ public String timed() {
+ testClock.tick++;
+ return "yay";
+ }
+
+ @GET
+ @Timed(name = "fancyName")
+ @Path("/named")
+ public String named() {
+ testClock.tick++;
+ return "fancy";
+ }
+
+ @GET
+ @Timed(name = "absolutelyFancy", absolute = true)
+ @Path("/absolute")
+ public String absolute() {
+ testClock.tick++;
+ return "absolute";
+ }
+
+ @Path("/subresource")
+ public InstrumentedFilteredSubResource locateSubResource() {
+ return new InstrumentedFilteredSubResource();
+ }
+
+ @Produces(MediaType.TEXT_PLAIN)
+ public class InstrumentedFilteredSubResource {
+
+ @GET
+ @Timed
+ @Path("/timed")
+ public String timed() {
+ testClock.tick += 2;
+ 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/InstrumentedResource.java
index 8b7bf33..963ac82 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
@@ -2,12 +2,22 @@ package com.codahale.metrics.jersey2.resources;
import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Metered;
+import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.Timed;
-import javax.ws.rs.*;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
import java.io.IOException;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+
@Path("/")
@Produces(MediaType.TEXT_PLAIN)
public class InstrumentedResource {
@@ -35,6 +45,27 @@ public class InstrumentedResource {
return "fuh";
}
+ @GET
+ @ResponseMetered(level = DETAILED)
+ @Path("/response-metered-detailed")
+ public Response responseMeteredDetailed(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
+ @GET
+ @ResponseMetered(level = COARSE)
+ @Path("/response-metered-coarse")
+ public Response responseMeteredCoarse(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
+ @GET
+ @ResponseMetered(level = ALL)
+ @Path("/response-metered-all")
+ public Response responseMeteredAll(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
@Path("/subresource")
public InstrumentedSubResource locateSubResource() {
return new InstrumentedSubResource();
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceExceptionMeteredPerClass.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceExceptionMeteredPerClass.java
index b5ac922..929faba 100644
--- a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceExceptionMeteredPerClass.java
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceExceptionMeteredPerClass.java
@@ -2,7 +2,11 @@ package com.codahale.metrics.jersey2.resources;
import com.codahale.metrics.annotation.ExceptionMetered;
-import javax.ws.rs.*;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceResponseMeteredPerClass.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceResponseMeteredPerClass.java
new file mode 100644
index 0000000..e03e8e9
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceResponseMeteredPerClass.java
@@ -0,0 +1,58 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.jersey2.exception.TestException;
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@ResponseMetered
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedResourceResponseMeteredPerClass {
+
+ @GET
+ @Path("/responseMetered2xxPerClass")
+ public Response responseMetered2xxPerClass() {
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/responseMetered4xxPerClass")
+ public Response responseMetered4xxPerClass() {
+ return Response.status(Response.Status.BAD_REQUEST).build();
+ }
+
+ @GET
+ @Path("/responseMetered5xxPerClass")
+ public Response responseMetered5xxPerClass() {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ @GET
+ @Path("/responseMeteredBadRequestPerClass")
+ public String responseMeteredBadRequestPerClass() {
+ throw new BadRequestException();
+ }
+
+ @GET
+ @Path("/responseMeteredRuntimeExceptionPerClass")
+ public String responseMeteredRuntimeExceptionPerClass() {
+ throw new RuntimeException();
+ }
+
+ @GET
+ @Path("/responseMeteredTestExceptionPerClass")
+ public String responseMeteredTestExceptionPerClass() {
+ throw new TestException("test");
+ }
+
+ @Path("/subresource")
+ public InstrumentedSubResourceResponseMeteredPerClass locateSubResource() {
+ return new InstrumentedSubResourceResponseMeteredPerClass();
+ }
+
+}
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
index 8ef7612..4c8f79f 100644
--- 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
@@ -2,12 +2,14 @@ package com.codahale.metrics.jersey2.resources;
import com.codahale.metrics.annotation.Timed;
-import javax.ws.rs.*;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
-import java.io.IOException;
@Produces(MediaType.TEXT_PLAIN)
public class InstrumentedSubResource {
+
@GET
@Timed
@Path("/timed")
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceExceptionMeteredPerClass.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
index 3a120aa..fec77ad 100644
--- a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
@@ -2,7 +2,11 @@ package com.codahale.metrics.jersey2.resources;
import com.codahale.metrics.annotation.ExceptionMetered;
-import javax.ws.rs.*;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceResponseMeteredPerClass.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceResponseMeteredPerClass.java
new file mode 100644
index 0000000..ae131ee
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceResponseMeteredPerClass.java
@@ -0,0 +1,22 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+
+@ResponseMetered(level = ALL)
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceResponseMeteredPerClass {
+ @GET
+ @Path("/responseMeteredPerClass")
+ public Response responseMeteredPerClass() {
+ return Response.status(Response.Status.OK).build();
+ }
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/TestRequestFilter.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/TestRequestFilter.java
new file mode 100644
index 0000000..3d6d639
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/TestRequestFilter.java
@@ -0,0 +1,21 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.jersey2.TestClock;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import java.io.IOException;
+
+public class TestRequestFilter implements ContainerRequestFilter {
+
+ private final TestClock testClock;
+
+ public TestRequestFilter(TestClock testClock) {
+ this.testClock = testClock;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext containerRequestContext) throws IOException {
+ testClock.tick += 4;
+ }
+}
diff --git a/metrics-jersey3/pom.xml b/metrics-jersey3/pom.xml
new file mode 100644
index 0000000..01caffc
--- /dev/null
+++ b/metrics-jersey3/pom.xml
@@ -0,0 +1,121 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jersey3</artifactId>
+ <name>Metrics Integration for Jersey 3.x</name>
+ <packaging>bundle</packaging>
+ <description>
+ A set of class providing Metrics integration for Jersey, the reference JAX-RS
+ implementation.
+ </description>
+
+ <properties>
+ <javaModuleName>com.codahale.metrics.jersey3</javaModuleName>
+ <jersey.version>3.0.12</jersey.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey</groupId>
+ <artifactId>jersey-bom</artifactId>
+ <version>${jersey.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.annotation</groupId>
+ <artifactId>jakarta.annotation-api</artifactId>
+ <version>2.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.inject</groupId>
+ <artifactId>jakarta.inject-api</artifactId>
+ <version>2.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.ws.rs</groupId>
+ <artifactId>jakarta.ws.rs-api</artifactId>
+ <version>3.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.13.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-inmemory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jersey3/src/main/java/com/codahale/metrics/jersey3/InstrumentedResourceMethodApplicationListener.java b/metrics-jersey3/src/main/java/com/codahale/metrics/jersey3/InstrumentedResourceMethodApplicationListener.java
new file mode 100644
index 0000000..0e0b39a
--- /dev/null
+++ b/metrics-jersey3/src/main/java/com/codahale/metrics/jersey3/InstrumentedResourceMethodApplicationListener.java
@@ -0,0 +1,550 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Reservoir;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ExceptionMetered;
+import com.codahale.metrics.annotation.Metered;
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.core.Configuration;
+import jakarta.ws.rs.ext.Provider;
+import org.glassfish.jersey.server.ContainerResponse;
+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 java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+
+/**
+ * An application event listener that listens for Jersey application initialization to
+ * be finished, then creates a map of resource method that have metrics annotations.
+ * <p>
+ * Finally, it listens for method start events, and returns a {@link RequestEventListener}
+ * that updates the relevant metric for suitably annotated methods when it gets the
+ * request events indicating that the method is about to be invoked, or just got done
+ * being invoked.
+ */
+@Provider
+public class InstrumentedResourceMethodApplicationListener implements ApplicationEventListener, ModelProcessor {
+
+ private static final String[] REQUEST_FILTERING = {"request", "filtering"};
+ private static final String[] RESPONSE_FILTERING = {"response", "filtering"};
+ private static final String TOTAL = "total";
+
+ private final MetricRegistry metrics;
+ private final ConcurrentMap<EventTypeAndMethod, Timer> timers = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, Meter> meters = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, ResponseMeterMetric> responseMeters = new ConcurrentHashMap<>();
+
+ private final Clock clock;
+ private final boolean trackFilters;
+ private final Supplier<Reservoir> reservoirSupplier;
+
+ /**
+ * Construct an application event listener using the given metrics registry.
+ * <p>
+ * When using this constructor, the {@link InstrumentedResourceMethodApplicationListener}
+ * should be added to a Jersey {@code ResourceConfig} as a singleton.
+ *
+ * @param metrics a {@link MetricRegistry}
+ */
+ public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics) {
+ this(metrics, Clock.defaultClock(), false);
+ }
+
+ /**
+ * Constructs a custom application listener.
+ *
+ * @param metrics the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ */
+ public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics, final Clock clock,
+ final boolean trackFilters) {
+ this(metrics, clock, trackFilters, ExponentiallyDecayingReservoir::new);
+ }
+
+ /**
+ * Constructs a custom application listener.
+ *
+ * @param metrics the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics, final Clock clock,
+ final boolean trackFilters,
+ final Supplier<Reservoir> reservoirSupplier) {
+ this.metrics = metrics;
+ this.clock = clock;
+ this.trackFilters = trackFilters;
+ this.reservoirSupplier = reservoirSupplier;
+ }
+
+ /**
+ * A private class to maintain the metric for a method annotated with the
+ * {@link ExceptionMetered} annotation, which needs to maintain both a meter
+ * and a cause for which the meter should be updated.
+ */
+ private static class ExceptionMeterMetric {
+ public final Meter meter;
+ public final Class<? extends Throwable> cause;
+
+ public ExceptionMeterMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final ExceptionMetered exceptionMetered) {
+ final String name = chooseName(exceptionMetered.name(),
+ exceptionMetered.absolute(), method, ExceptionMetered.DEFAULT_NAME_SUFFIX);
+ this.meter = registry.meter(name);
+ this.cause = exceptionMetered.cause();
+ }
+ }
+
+ /**
+ * A private class to maintain the metrics for a method annotated with the
+ * {@link ResponseMetered} annotation, which needs to maintain meters for
+ * different response codes
+ */
+ private static class ResponseMeterMetric {
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+ private final List<Meter> meters;
+ private final Map<Integer, Meter> responseCodeMeters;
+ private final MetricRegistry metricRegistry;
+ private final String metricName;
+ private final ResponseMeteredLevel level;
+
+ public ResponseMeterMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final ResponseMetered responseMetered) {
+ this.metricName = chooseName(responseMetered.name(), responseMetered.absolute(), method);
+ this.level = responseMetered.level();
+ this.meters = COARSE_METER_LEVELS.contains(level) ?
+ Collections.unmodifiableList(Arrays.asList(
+ registry.meter(name(metricName, "1xx-responses")), // 1xx
+ registry.meter(name(metricName, "2xx-responses")), // 2xx
+ registry.meter(name(metricName, "3xx-responses")), // 3xx
+ registry.meter(name(metricName, "4xx-responses")), // 4xx
+ registry.meter(name(metricName, "5xx-responses")) // 5xx
+ )) : Collections.emptyList();
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(level) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+ this.metricRegistry = registry;
+ }
+
+ public void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(level)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(level)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ meters.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(metricName, String.format("%d-responses", sc))));
+ }
+ }
+
+ private static class TimerRequestEventListener implements RequestEventListener {
+
+ private final ConcurrentMap<EventTypeAndMethod, Timer> timers;
+ private final Clock clock;
+ private final long start;
+ private Timer.Context resourceMethodStartContext;
+ private Timer.Context requestMatchedContext;
+ private Timer.Context responseFiltersStartContext;
+
+ public TimerRequestEventListener(final ConcurrentMap<EventTypeAndMethod, Timer> timers, final Clock clock) {
+ this.timers = timers;
+ this.clock = clock;
+ start = clock.getTick();
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ switch (event.getType()) {
+ case RESOURCE_METHOD_START:
+ resourceMethodStartContext = context(event);
+ break;
+ case REQUEST_MATCHED:
+ requestMatchedContext = context(event);
+ break;
+ case RESP_FILTERS_START:
+ responseFiltersStartContext = context(event);
+ break;
+ case RESOURCE_METHOD_FINISHED:
+ if (resourceMethodStartContext != null) {
+ resourceMethodStartContext.close();
+ }
+ break;
+ case REQUEST_FILTERED:
+ if (requestMatchedContext != null) {
+ requestMatchedContext.close();
+ }
+ break;
+ case RESP_FILTERS_FINISHED:
+ if (responseFiltersStartContext != null) {
+ responseFiltersStartContext.close();
+ }
+ break;
+ case FINISHED:
+ if (requestMatchedContext != null && responseFiltersStartContext != null) {
+ final Timer timer = timer(event);
+ if (timer != null) {
+ timer.update(clock.getTick() - start, TimeUnit.NANOSECONDS);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private Timer timer(RequestEvent event) {
+ final ResourceMethod resourceMethod = event.getUriInfo().getMatchedResourceMethod();
+ if (resourceMethod == null) {
+ return null;
+ }
+ return timers.get(new EventTypeAndMethod(event.getType(), resourceMethod.getInvocable().getDefinitionMethod()));
+ }
+
+ private Timer.Context context(RequestEvent event) {
+ final Timer timer = timer(event);
+ return timer != null ? timer.time() : null;
+ }
+ }
+
+ private static class MeterRequestEventListener implements RequestEventListener {
+ private final ConcurrentMap<Method, Meter> meters;
+
+ public MeterRequestEventListener(final ConcurrentMap<Method, Meter> meters) {
+ this.meters = meters;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ if (event.getType() == RequestEvent.Type.RESOURCE_METHOD_START) {
+ final Meter meter = this.meters.get(event.getUriInfo().getMatchedResourceMethod().getInvocable().getDefinitionMethod());
+ if (meter != null) {
+ meter.mark();
+ }
+ }
+ }
+ }
+
+ private static class ExceptionMeterRequestEventListener implements RequestEventListener {
+ private final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters;
+
+ public ExceptionMeterRequestEventListener(final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters) {
+ this.exceptionMeters = exceptionMeters;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ if (event.getType() == RequestEvent.Type.ON_EXCEPTION) {
+ final ResourceMethod method = event.getUriInfo().getMatchedResourceMethod();
+ final ExceptionMeterMetric metric = (method != null) ?
+ this.exceptionMeters.get(method.getInvocable().getDefinitionMethod()) : null;
+
+ if (metric != null) {
+ if (metric.cause.isAssignableFrom(event.getException().getClass()) ||
+ (event.getException().getCause() != null &&
+ metric.cause.isAssignableFrom(event.getException().getCause().getClass()))) {
+ metric.meter.mark();
+ }
+ }
+ }
+ }
+ }
+
+ private static class ResponseMeterRequestEventListener implements RequestEventListener {
+ private final ConcurrentMap<Method, ResponseMeterMetric> responseMeters;
+
+ public ResponseMeterRequestEventListener(final ConcurrentMap<Method, ResponseMeterMetric> responseMeters) {
+ this.responseMeters = responseMeters;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ if (event.getType() == RequestEvent.Type.FINISHED) {
+ final ResourceMethod method = event.getUriInfo().getMatchedResourceMethod();
+ final ResponseMeterMetric metric = (method != null) ?
+ this.responseMeters.get(method.getInvocable().getDefinitionMethod()) : null;
+
+ if (metric != null) {
+ ContainerResponse containerResponse = event.getContainerResponse();
+ if (containerResponse == null && event.getException() != null) {
+ metric.mark(500);
+ } else if (containerResponse != null) {
+ metric.mark(containerResponse.getStatus());
+ }
+ }
+ }
+ }
+ }
+
+ private static class ChainedRequestEventListener implements RequestEventListener {
+ private final RequestEventListener[] listeners;
+
+ private ChainedRequestEventListener(final RequestEventListener... listeners) {
+ this.listeners = listeners;
+ }
+
+ @Override
+ public void onEvent(final RequestEvent event) {
+ for (RequestEventListener listener : listeners) {
+ listener.onEvent(event);
+ }
+ }
+ }
+
+ @Override
+ public void onEvent(ApplicationEvent event) {
+ if (event.getType() == ApplicationEvent.Type.INITIALIZATION_APP_FINISHED) {
+ registerMetricsForModel(event.getResourceModel());
+ }
+ }
+
+ @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);
+ final ResponseMetered classLevelResponseMetered = getClassLevelAnnotation(resource, ResponseMetered.class);
+
+ for (final ResourceMethod method : resource.getAllMethods()) {
+ registerTimedAnnotations(method, classLevelTimed);
+ registerMeteredAnnotations(method, classLevelMetered);
+ registerExceptionMeteredAnnotations(method, classLevelExceptionMetered);
+ registerResponseMeteredAnnotations(method, classLevelResponseMetered);
+ }
+
+ 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);
+ final ResponseMetered classLevelResponseMeteredChild = getClassLevelAnnotation(childResource, ResponseMetered.class);
+
+ for (final ResourceMethod method : childResource.getAllMethods()) {
+ registerTimedAnnotations(method, classLevelTimedChild);
+ registerMeteredAnnotations(method, classLevelMeteredChild);
+ registerExceptionMeteredAnnotations(method, classLevelExceptionMeteredChild);
+ registerResponseMeteredAnnotations(method, classLevelResponseMeteredChild);
+ }
+ }
+ }
+ }
+
+ @Override
+ public RequestEventListener onRequest(final RequestEvent event) {
+ final RequestEventListener listener = new ChainedRequestEventListener(
+ new TimerRequestEventListener(timers, clock),
+ new MeterRequestEventListener(meters),
+ new ExceptionMeterRequestEventListener(exceptionMeters),
+ new ResponseMeterRequestEventListener(responseMeters));
+
+ return listener;
+ }
+
+ 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) {
+ registerTimers(method, definitionMethod, classLevelTimed);
+ return;
+ }
+
+ final Timed annotation = definitionMethod.getAnnotation(Timed.class);
+ if (annotation != null) {
+ registerTimers(method, definitionMethod, annotation);
+ }
+ }
+
+ private void registerTimers(ResourceMethod method, Method definitionMethod, Timed annotation) {
+ timers.putIfAbsent(EventTypeAndMethod.requestMethodStart(definitionMethod), timerMetric(metrics, method, annotation));
+ if (trackFilters) {
+ timers.putIfAbsent(EventTypeAndMethod.requestMatched(definitionMethod), timerMetric(metrics, method, annotation, REQUEST_FILTERING));
+ timers.putIfAbsent(EventTypeAndMethod.respFiltersStart(definitionMethod), timerMetric(metrics, method, annotation, RESPONSE_FILTERING));
+ timers.putIfAbsent(EventTypeAndMethod.finished(definitionMethod), timerMetric(metrics, method, annotation, TOTAL));
+ }
+ }
+
+ 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) {
+ meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, annotation));
+ }
+ }
+
+ 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) {
+ exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, annotation));
+ }
+ }
+
+ private void registerResponseMeteredAnnotations(final ResourceMethod method, final ResponseMetered classLevelResponseMetered) {
+ final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+
+ if (classLevelResponseMetered != null) {
+ responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, classLevelResponseMetered));
+ return;
+ }
+ final ResponseMetered annotation = definitionMethod.getAnnotation(ResponseMetered.class);
+
+ if (annotation != null) {
+ responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, annotation));
+ }
+ }
+
+ private Timer timerMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final Timed timed,
+ final String... suffixes) {
+ final String name = chooseName(timed.name(), timed.absolute(), method, suffixes);
+ return registry.timer(name, () -> new Timer(reservoirSupplier.get(), clock));
+ }
+
+ private Meter meterMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final Metered metered) {
+ final String name = chooseName(metered.name(), metered.absolute(), method);
+ return registry.meter(name, () -> new Meter(clock));
+ }
+
+ protected static String chooseName(final String explicitName, final boolean absolute, final ResourceMethod method,
+ final String... suffixes) {
+ final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+ final String metricName;
+ if (explicitName != null && !explicitName.isEmpty()) {
+ metricName = absolute ? explicitName : name(definitionMethod.getDeclaringClass(), explicitName);
+ } else {
+ metricName = name(definitionMethod.getDeclaringClass(), definitionMethod.getName());
+ }
+ return name(metricName, suffixes);
+ }
+
+ private static class EventTypeAndMethod {
+
+ private final RequestEvent.Type type;
+ private final Method method;
+
+ private EventTypeAndMethod(RequestEvent.Type type, Method method) {
+ this.type = type;
+ this.method = method;
+ }
+
+ static EventTypeAndMethod requestMethodStart(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.RESOURCE_METHOD_START, method);
+ }
+
+ static EventTypeAndMethod requestMatched(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.REQUEST_MATCHED, method);
+ }
+
+ static EventTypeAndMethod respFiltersStart(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.RESP_FILTERS_START, method);
+ }
+
+ static EventTypeAndMethod finished(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.FINISHED, method);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EventTypeAndMethod that = (EventTypeAndMethod) o;
+
+ if (type != that.type) {
+ return false;
+ }
+ return method.equals(that.method);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type.hashCode();
+ result = 31 * result + method.hashCode();
+ return result;
+ }
+ }
+}
diff --git a/metrics-jersey3/src/main/java/com/codahale/metrics/jersey3/MetricsFeature.java b/metrics-jersey3/src/main/java/com/codahale/metrics/jersey3/MetricsFeature.java
new file mode 100644
index 0000000..7ed38b1
--- /dev/null
+++ b/metrics-jersey3/src/main/java/com/codahale/metrics/jersey3/MetricsFeature.java
@@ -0,0 +1,97 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Reservoir;
+import com.codahale.metrics.SharedMetricRegistries;
+import jakarta.ws.rs.core.Feature;
+import jakarta.ws.rs.core.FeatureContext;
+
+import java.util.function.Supplier;
+
+/**
+ * A {@link Feature} which registers a {@link InstrumentedResourceMethodApplicationListener}
+ * for recording request events.
+ */
+public class MetricsFeature implements Feature {
+
+ private final MetricRegistry registry;
+ private final Clock clock;
+ private final boolean trackFilters;
+ private final Supplier<Reservoir> reservoirSupplier;
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ */
+ public MetricsFeature(MetricRegistry registry) {
+ this(registry, Clock.defaultClock());
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public MetricsFeature(MetricRegistry registry, Supplier<Reservoir> reservoirSupplier) {
+ this(registry, Clock.defaultClock(), false, reservoirSupplier);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock) {
+ this(registry, clock, false);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock, boolean trackFilters) {
+ this(registry, clock, trackFilters, ExponentiallyDecayingReservoir::new);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock, boolean trackFilters, Supplier<Reservoir> reservoirSupplier) {
+ this.registry = registry;
+ this.clock = clock;
+ this.trackFilters = trackFilters;
+ this.reservoirSupplier = reservoirSupplier;
+ }
+
+ public MetricsFeature(String registryName) {
+ this(SharedMetricRegistries.getOrCreate(registryName));
+ }
+
+ /**
+ * A call-back method called when the feature is to be enabled in a given
+ * runtime configuration scope.
+ * <p>
+ * The responsibility of the feature is to properly update the supplied runtime configuration context
+ * and return {@code true} if the feature was successfully enabled or {@code false} otherwise.
+ * <p>
+ * Note that under some circumstances the feature may decide not to enable itself, which
+ * is indicated by returning {@code false}. In such case the configuration context does
+ * not add the feature to the collection of enabled features and a subsequent call to
+ * {@link jakarta.ws.rs.core.Configuration#isEnabled(jakarta.ws.rs.core.Feature)} or
+ * {@link jakarta.ws.rs.core.Configuration#isEnabled(Class)} method
+ * would return {@code false}.
+ * <p>
+ *
+ * @param context configurable context in which the feature should be enabled.
+ * @return {@code true} if the feature was successfully enabled, {@code false}
+ * otherwise.
+ */
+ @Override
+ public boolean configure(FeatureContext context) {
+ context.register(new InstrumentedResourceMethodApplicationListener(registry, clock, trackFilters, reservoirSupplier));
+ return true;
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/CustomReservoirImplementationTest.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/CustomReservoirImplementationTest.java
new file mode 100644
index 0000000..4999ee5
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/CustomReservoirImplementationTest.java
@@ -0,0 +1,44 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.UniformReservoir;
+import com.codahale.metrics.jersey3.resources.InstrumentedResourceTimedPerClass;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+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;
+
+public class CustomReservoirImplementationTest extends JerseyTest {
+ static {
+ Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
+ }
+
+ private MetricRegistry registry;
+
+ @Override
+ protected Application configure() {
+ this.registry = new MetricRegistry();
+
+ return new ResourceConfig()
+ .register(new MetricsFeature(this.registry, UniformReservoir::new))
+ .register(InstrumentedResourceTimedPerClass.class);
+ }
+
+ @Test
+ public void timerHistogramIsUsingCustomReservoirImplementation() {
+ assertThat(target("timedPerClass").request().get(String.class)).isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedResourceTimedPerClass.class, "timedPerClass"));
+ assertThat(timer)
+ .extracting("histogram")
+ .extracting("reservoir")
+ .isInstanceOf(UniformReservoir.class);
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonFilterMetricsJerseyTest.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonFilterMetricsJerseyTest.java
new file mode 100644
index 0000000..aa5e2f1
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonFilterMetricsJerseyTest.java
@@ -0,0 +1,162 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.jersey3.resources.InstrumentedFilteredResource;
+import com.codahale.metrics.jersey3.resources.TestRequestFilter;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Before;
+import org.junit.Test;
+
+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} with filter tracking
+ */
+public class SingletonFilterMetricsJerseyTest extends JerseyTest {
+ static {
+ Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
+ }
+
+ private MetricRegistry registry;
+
+ private TestClock testClock;
+
+ @Override
+ protected Application configure() {
+ registry = new MetricRegistry();
+ testClock = new TestClock();
+ ResourceConfig config = new ResourceConfig();
+ config = config.register(new MetricsFeature(this.registry, testClock, true));
+ config = config.register(new TestRequestFilter(testClock));
+ config = config.register(new InstrumentedFilteredResource(testClock));
+ return config;
+ }
+
+ @Before
+ public void resetClock() {
+ testClock.tick = 0;
+ }
+
+ @Test
+ public void timedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void explicitNamesAreTimed() {
+ assertThat(target("named")
+ .request()
+ .get(String.class))
+ .isEqualTo("fancy");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "fancyName"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void absoluteNamesAreTimed() {
+ assertThat(target("absolute")
+ .request()
+ .get(String.class))
+ .isEqualTo("absolute");
+
+ final Timer timer = registry.timer("absolutelyFancy");
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void requestFiltersOfTimedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "request", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void responseFiltersOfTimedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "response", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void totalTimeOfTimedMethodsIsTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "total"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(5);
+ }
+
+ @Test
+ public void requestFiltersOfNamedMethodsAreTimed() {
+ assertThat(target("named")
+ .request()
+ .get(String.class))
+ .isEqualTo("fancy");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "fancyName", "request", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void requestFiltersOfAbsoluteMethodsAreTimed() {
+ assertThat(target("absolute")
+ .request()
+ .get(String.class))
+ .isEqualTo("absolute");
+
+ final Timer timer = registry.timer(name("absolutelyFancy", "request", "filtering"));
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void subResourcesFromLocatorsRegisterMetrics() {
+ assertThat(target("subresource/timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.InstrumentedFilteredSubResource.class,
+ "timed"));
+ assertThat(timer.getCount()).isEqualTo(1);
+
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsExceptionMeteredPerClassJerseyTest.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsExceptionMeteredPerClassJerseyTest.java
new file mode 100644
index 0000000..d20387f
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsExceptionMeteredPerClassJerseyTest.java
@@ -0,0 +1,98 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jersey3.resources.InstrumentedResourceExceptionMeteredPerClass;
+import com.codahale.metrics.jersey3.resources.InstrumentedSubResourceExceptionMeteredPerClass;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import java.io.IOException;
+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;
+import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
+
+/**
+ * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
+ * in a Jersey {@link ResourceConfig}
+ */
+public class SingletonMetricsExceptionMeteredPerClassJerseyTest 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(InstrumentedResourceExceptionMeteredPerClass.class);
+
+ return config;
+ }
+
+ @Test
+ public void exceptionMeteredMethodsAreExceptionMetered() {
+ final Meter meter = registry.meter(name(InstrumentedResourceExceptionMeteredPerClass.class,
+ "exceptionMetered",
+ "exceptions"));
+
+ assertThat(target("exception-metered")
+ .request()
+ .get(String.class))
+ .isEqualTo("fuh");
+
+ assertThat(meter.getCount()).isZero();
+
+ try {
+ target("exception-metered")
+ .queryParam("splode", true)
+ .request()
+ .get(String.class);
+
+ failBecauseExceptionWasNotThrown(ProcessingException.class);
+ } catch (ProcessingException e) {
+ assertThat(e.getCause()).isInstanceOf(IOException.class);
+ }
+
+ assertThat(meter.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void subresourcesFromLocatorsRegisterMetrics() {
+ final Meter meter = registry.meter(name(InstrumentedSubResourceExceptionMeteredPerClass.class,
+ "exceptionMetered",
+ "exceptions"));
+
+ assertThat(target("subresource/exception-metered")
+ .request()
+ .get(String.class))
+ .isEqualTo("fuh");
+
+ assertThat(meter.getCount()).isZero();
+
+ try {
+ target("subresource/exception-metered")
+ .queryParam("splode", true)
+ .request()
+ .get(String.class);
+
+ failBecauseExceptionWasNotThrown(ProcessingException.class);
+ } catch (ProcessingException e) {
+ assertThat(e.getCause()).isInstanceOf(IOException.class);
+ }
+
+ assertThat(meter.getCount()).isEqualTo(1);
+ }
+
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsJerseyTest.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsJerseyTest.java
new file mode 100644
index 0000000..bb5afd3
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsJerseyTest.java
@@ -0,0 +1,171 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.jersey3.resources.InstrumentedResource;
+import com.codahale.metrics.jersey3.resources.InstrumentedSubResource;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import org.glassfish.jersey.client.ClientResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import java.io.IOException;
+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;
+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);
+ }
+
+ 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(InstrumentedResource.class);
+
+ return config;
+ }
+
+ @Test
+ public void timedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedResource.class, "timed"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void meteredMethodsAreMetered() {
+ assertThat(target("metered")
+ .request()
+ .get(String.class))
+ .isEqualTo("woo");
+
+ 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,
+ "exceptionMetered",
+ "exceptions"));
+
+ assertThat(target("exception-metered")
+ .request()
+ .get(String.class))
+ .isEqualTo("fuh");
+
+ assertThat(meter.getCount()).isZero();
+
+ try {
+ target("exception-metered")
+ .queryParam("splode", true)
+ .request()
+ .get(String.class);
+
+ failBecauseExceptionWasNotThrown(ProcessingException.class);
+ } catch (ProcessingException e) {
+ assertThat(e.getCause()).isInstanceOf(IOException.class);
+ }
+
+ assertThat(meter.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMeteredMethodsAreMeteredWithDetailedLevel() {
+ final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "200-responses"));
+ final Meter meter201 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "201-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(meter201.getCount()).isZero();
+ assertThat(target("response-metered-detailed")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+ assertThat(target("response-metered-detailed")
+ .queryParam("status_code", 201)
+ .request()
+ .get().getStatus())
+ .isEqualTo(201);
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isOne();
+ assertThat(meter201.getCount()).isOne();
+ }
+
+ @Test
+ public void responseMeteredMethodsAreMeteredWithAllLevel() {
+ final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredAll",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredAll",
+ "200-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(target("response-metered-all")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ assertThat(meter2xx.getCount()).isOne();
+ assertThat(meter200.getCount()).isOne();
+ }
+
+ @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");
+ }
+ }
+
+ @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-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsMeteredPerClassJerseyTest.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsMeteredPerClassJerseyTest.java
new file mode 100644
index 0000000..0e71640
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsMeteredPerClassJerseyTest.java
@@ -0,0 +1,66 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jersey3.resources.InstrumentedResourceMeteredPerClass;
+import com.codahale.metrics.jersey3.resources.InstrumentedSubResourceMeteredPerClass;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+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-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsResponseMeteredPerClassJerseyTest.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsResponseMeteredPerClassJerseyTest.java
new file mode 100644
index 0000000..f85b698
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsResponseMeteredPerClassJerseyTest.java
@@ -0,0 +1,146 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jersey3.exception.mapper.TestExceptionMapper;
+import com.codahale.metrics.jersey3.resources.InstrumentedResourceResponseMeteredPerClass;
+import com.codahale.metrics.jersey3.resources.InstrumentedSubResourceResponseMeteredPerClass;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+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;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
+ * in a Jersey {@link ResourceConfig}
+ */
+public class SingletonMetricsResponseMeteredPerClassJerseyTest 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(InstrumentedResourceResponseMeteredPerClass.class);
+ config = config.register(new TestExceptionMapper());
+
+ return config;
+ }
+
+ @Test
+ public void responseMetered2xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered2xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ final Meter meter2xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered2xxPerClass",
+ "2xx-responses"));
+
+ assertThat(meter2xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMetered4xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered4xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(400);
+ assertThat(target("responseMeteredBadRequestPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(400);
+
+ final Meter meter4xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered4xxPerClass",
+ "4xx-responses"));
+ final Meter meterException4xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredBadRequestPerClass",
+ "4xx-responses"));
+
+ assertThat(meter4xx.getCount()).isEqualTo(1);
+ assertThat(meterException4xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMetered5xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered5xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(500);
+
+ final Meter meter5xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered5xxPerClass",
+ "5xx-responses"));
+
+ assertThat(meter5xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMeteredMappedExceptionPerClassMethodsAreMetered() {
+ assertThat(target("responseMeteredTestExceptionPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(500);
+
+ final Meter meterTestException = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredTestExceptionPerClass",
+ "5xx-responses"));
+
+ assertThat(meterTestException.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMeteredUnmappedExceptionPerClassMethodsAreMetered() {
+ try {
+ target("responseMeteredRuntimeExceptionPerClass")
+ .request()
+ .get();
+ fail("expected RuntimeException");
+ } catch (Exception e) {
+ assertThat(e.getCause()).isInstanceOf(RuntimeException.class);
+ }
+
+ final Meter meterException5xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredRuntimeExceptionPerClass",
+ "5xx-responses"));
+
+ assertThat(meterException5xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void subresourcesFromLocatorsRegisterMetrics() {
+ final Meter meter2xx = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
+ "responseMeteredPerClass",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
+ "responseMeteredPerClass",
+ "200-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(target("subresource/responseMeteredPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ assertThat(meter2xx.getCount()).isOne();
+ assertThat(meter200.getCount()).isOne();
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsTimedPerClassJerseyTest.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsTimedPerClassJerseyTest.java
new file mode 100644
index 0000000..0249e15
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/SingletonMetricsTimedPerClassJerseyTest.java
@@ -0,0 +1,66 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.jersey3.resources.InstrumentedResourceTimedPerClass;
+import com.codahale.metrics.jersey3.resources.InstrumentedSubResourceTimedPerClass;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+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-jersey3/src/test/java/com/codahale/metrics/jersey3/TestClock.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/TestClock.java
new file mode 100644
index 0000000..1e6f5a2
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/TestClock.java
@@ -0,0 +1,13 @@
+package com.codahale.metrics.jersey3;
+
+import com.codahale.metrics.Clock;
+
+public class TestClock extends Clock {
+
+ public long tick;
+
+ @Override
+ public long getTick() {
+ return tick;
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/exception/TestException.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/exception/TestException.java
new file mode 100644
index 0000000..49beb0d
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/exception/TestException.java
@@ -0,0 +1,9 @@
+package com.codahale.metrics.jersey3.exception;
+
+public class TestException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public TestException(String message) {
+ super(message);
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/exception/mapper/TestExceptionMapper.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/exception/mapper/TestExceptionMapper.java
new file mode 100644
index 0000000..45c2984
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/exception/mapper/TestExceptionMapper.java
@@ -0,0 +1,14 @@
+package com.codahale.metrics.jersey3.exception.mapper;
+
+import com.codahale.metrics.jersey3.exception.TestException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+@Provider
+public class TestExceptionMapper implements ExceptionMapper<TestException> {
+ @Override
+ public Response toResponse(TestException exception) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedFilteredResource.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedFilteredResource.java
new file mode 100644
index 0000000..ac379a7
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedFilteredResource.java
@@ -0,0 +1,61 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import com.codahale.metrics.jersey3.TestClock;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedFilteredResource {
+
+ private final TestClock testClock;
+
+ public InstrumentedFilteredResource(TestClock testClock) {
+ this.testClock = testClock;
+ }
+
+ @GET
+ @Timed
+ @Path("/timed")
+ public String timed() {
+ testClock.tick++;
+ return "yay";
+ }
+
+ @GET
+ @Timed(name = "fancyName")
+ @Path("/named")
+ public String named() {
+ testClock.tick++;
+ return "fancy";
+ }
+
+ @GET
+ @Timed(name = "absolutelyFancy", absolute = true)
+ @Path("/absolute")
+ public String absolute() {
+ testClock.tick++;
+ return "absolute";
+ }
+
+ @Path("/subresource")
+ public InstrumentedFilteredSubResource locateSubResource() {
+ return new InstrumentedFilteredSubResource();
+ }
+
+ @Produces(MediaType.TEXT_PLAIN)
+ public class InstrumentedFilteredSubResource {
+
+ @GET
+ @Timed
+ @Path("/timed")
+ public String timed() {
+ testClock.tick += 2;
+ return "yay";
+ }
+
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResource.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResource.java
new file mode 100644
index 0000000..ca0437f
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResource.java
@@ -0,0 +1,72 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.ExceptionMetered;
+import com.codahale.metrics.annotation.Metered;
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.io.IOException;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+
+@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";
+ }
+
+ @GET
+ @ExceptionMetered(cause = IOException.class)
+ @Path("/exception-metered")
+ public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
+ if (splode) {
+ throw new IOException("AUGH");
+ }
+ return "fuh";
+ }
+
+ @GET
+ @ResponseMetered(level = DETAILED)
+ @Path("/response-metered-detailed")
+ public Response responseMeteredDetailed(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
+ @GET
+ @ResponseMetered(level = COARSE)
+ @Path("/response-metered-coarse")
+ public Response responseMeteredCoarse(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
+ @GET
+ @ResponseMetered(level = ALL)
+ @Path("/response-metered-all")
+ public Response responseMeteredAll(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
+ @Path("/subresource")
+ public InstrumentedSubResource locateSubResource() {
+ return new InstrumentedSubResource();
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceExceptionMeteredPerClass.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceExceptionMeteredPerClass.java
new file mode 100644
index 0000000..b60c5ba
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceExceptionMeteredPerClass.java
@@ -0,0 +1,32 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.ExceptionMetered;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+
+import java.io.IOException;
+
+@ExceptionMetered(cause = IOException.class)
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedResourceExceptionMeteredPerClass {
+
+ @GET
+ @Path("/exception-metered")
+ public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
+ if (splode) {
+ throw new IOException("AUGH");
+ }
+ return "fuh";
+ }
+
+ @Path("/subresource")
+ public InstrumentedSubResourceExceptionMeteredPerClass locateSubResource() {
+ return new InstrumentedSubResourceExceptionMeteredPerClass();
+ }
+
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceMeteredPerClass.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceMeteredPerClass.java
new file mode 100644
index 0000000..232ec31
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceMeteredPerClass.java
@@ -0,0 +1,25 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.Metered;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Metered
+@Path("/")
+@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-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceResponseMeteredPerClass.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceResponseMeteredPerClass.java
new file mode 100644
index 0000000..062541d
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceResponseMeteredPerClass.java
@@ -0,0 +1,58 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.jersey3.exception.TestException;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+@ResponseMetered
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedResourceResponseMeteredPerClass {
+
+ @GET
+ @Path("/responseMetered2xxPerClass")
+ public Response responseMetered2xxPerClass() {
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/responseMetered4xxPerClass")
+ public Response responseMetered4xxPerClass() {
+ return Response.status(Response.Status.BAD_REQUEST).build();
+ }
+
+ @GET
+ @Path("/responseMetered5xxPerClass")
+ public Response responseMetered5xxPerClass() {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ @GET
+ @Path("/responseMeteredBadRequestPerClass")
+ public String responseMeteredBadRequestPerClass() {
+ throw new BadRequestException();
+ }
+
+ @GET
+ @Path("/responseMeteredRuntimeExceptionPerClass")
+ public String responseMeteredRuntimeExceptionPerClass() {
+ throw new RuntimeException();
+ }
+
+ @GET
+ @Path("/responseMeteredTestExceptionPerClass")
+ public String responseMeteredTestExceptionPerClass() {
+ throw new TestException("test");
+ }
+
+ @Path("/subresource")
+ public InstrumentedSubResourceResponseMeteredPerClass locateSubResource() {
+ return new InstrumentedSubResourceResponseMeteredPerClass();
+ }
+
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceTimedPerClass.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceTimedPerClass.java
new file mode 100644
index 0000000..1d91d21
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedResourceTimedPerClass.java
@@ -0,0 +1,25 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Timed
+@Path("/")
+@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-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResource.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResource.java
new file mode 100644
index 0000000..b1a3592
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResource.java
@@ -0,0 +1,19 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResource {
+
+ @GET
+ @Timed
+ @Path("/timed")
+ public String timed() {
+ return "yay";
+ }
+
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceExceptionMeteredPerClass.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
new file mode 100644
index 0000000..4a1c5b2
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
@@ -0,0 +1,24 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.ExceptionMetered;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+
+import java.io.IOException;
+
+@ExceptionMetered(cause = IOException.class)
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceExceptionMeteredPerClass {
+ @GET
+ @Path("/exception-metered")
+ public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
+ if (splode) {
+ throw new IOException("AUGH");
+ }
+ return "fuh";
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceMeteredPerClass.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceMeteredPerClass.java
new file mode 100644
index 0000000..5db617b
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceMeteredPerClass.java
@@ -0,0 +1,17 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.Metered;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Metered
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceMeteredPerClass {
+ @GET
+ @Path("/meteredPerClass")
+ public String meteredPerClass() {
+ return "yay";
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceResponseMeteredPerClass.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceResponseMeteredPerClass.java
new file mode 100644
index 0000000..3e74426
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceResponseMeteredPerClass.java
@@ -0,0 +1,20 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+
+@ResponseMetered(level = ALL)
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceResponseMeteredPerClass {
+ @GET
+ @Path("/responseMeteredPerClass")
+ public Response responseMeteredPerClass() {
+ return Response.status(Response.Status.OK).build();
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceTimedPerClass.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceTimedPerClass.java
new file mode 100644
index 0000000..538b9f9
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/InstrumentedSubResourceTimedPerClass.java
@@ -0,0 +1,17 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Timed
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceTimedPerClass {
+ @GET
+ @Path("/timedPerClass")
+ public String timedPerClass() {
+ return "yay";
+ }
+}
diff --git a/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/TestRequestFilter.java b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/TestRequestFilter.java
new file mode 100644
index 0000000..df6787c
--- /dev/null
+++ b/metrics-jersey3/src/test/java/com/codahale/metrics/jersey3/resources/TestRequestFilter.java
@@ -0,0 +1,21 @@
+package com.codahale.metrics.jersey3.resources;
+
+import com.codahale.metrics.jersey3.TestClock;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+
+import java.io.IOException;
+
+public class TestRequestFilter implements ContainerRequestFilter {
+
+ private final TestClock testClock;
+
+ public TestRequestFilter(TestClock testClock) {
+ this.testClock = testClock;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext containerRequestContext) throws IOException {
+ testClock.tick += 4;
+ }
+}
diff --git a/metrics-jersey31/pom.xml b/metrics-jersey31/pom.xml
new file mode 100644
index 0000000..83ed0e0
--- /dev/null
+++ b/metrics-jersey31/pom.xml
@@ -0,0 +1,121 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jersey31</artifactId>
+ <name>Metrics Integration for Jersey 3.1.x</name>
+ <packaging>bundle</packaging>
+ <description>
+ A set of class providing Metrics integration for Jersey 3.1.x, the reference JAX-RS
+ implementation.
+ </description>
+
+ <properties>
+ <javaModuleName>com.codahale.metrics.jersey31</javaModuleName>
+ <jersey.version>3.1.5</jersey.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey</groupId>
+ <artifactId>jersey-bom</artifactId>
+ <version>${jersey.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.annotation</groupId>
+ <artifactId>jakarta.annotation-api</artifactId>
+ <version>2.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.inject</groupId>
+ <artifactId>jakarta.inject-api</artifactId>
+ <version>2.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.ws.rs</groupId>
+ <artifactId>jakarta.ws.rs-api</artifactId>
+ <version>3.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.13.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-inmemory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jersey31/src/main/java/io/dropwizard/metrics/jersey31/InstrumentedResourceMethodApplicationListener.java b/metrics-jersey31/src/main/java/io/dropwizard/metrics/jersey31/InstrumentedResourceMethodApplicationListener.java
new file mode 100644
index 0000000..eeaf808
--- /dev/null
+++ b/metrics-jersey31/src/main/java/io/dropwizard/metrics/jersey31/InstrumentedResourceMethodApplicationListener.java
@@ -0,0 +1,549 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Reservoir;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ExceptionMetered;
+import com.codahale.metrics.annotation.Metered;
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.core.Configuration;
+import jakarta.ws.rs.ext.Provider;
+import org.glassfish.jersey.server.ContainerResponse;
+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 java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+/**
+ * An application event listener that listens for Jersey application initialization to
+ * be finished, then creates a map of resource method that have metrics annotations.
+ * <p>
+ * Finally, it listens for method start events, and returns a {@link RequestEventListener}
+ * that updates the relevant metric for suitably annotated methods when it gets the
+ * request events indicating that the method is about to be invoked, or just got done
+ * being invoked.
+ */
+@Provider
+public class InstrumentedResourceMethodApplicationListener implements ApplicationEventListener, ModelProcessor {
+
+ private static final String[] REQUEST_FILTERING = {"request", "filtering"};
+ private static final String[] RESPONSE_FILTERING = {"response", "filtering"};
+ private static final String TOTAL = "total";
+
+ private final MetricRegistry metrics;
+ private final ConcurrentMap<EventTypeAndMethod, Timer> timers = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, Meter> meters = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, ResponseMeterMetric> responseMeters = new ConcurrentHashMap<>();
+
+ private final Clock clock;
+ private final boolean trackFilters;
+ private final Supplier<Reservoir> reservoirSupplier;
+
+ /**
+ * Construct an application event listener using the given metrics registry.
+ * <p>
+ * When using this constructor, the {@link InstrumentedResourceMethodApplicationListener}
+ * should be added to a Jersey {@code ResourceConfig} as a singleton.
+ *
+ * @param metrics a {@link MetricRegistry}
+ */
+ public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics) {
+ this(metrics, Clock.defaultClock(), false);
+ }
+
+ /**
+ * Constructs a custom application listener.
+ *
+ * @param metrics the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ */
+ public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics, final Clock clock,
+ final boolean trackFilters) {
+ this(metrics, clock, trackFilters, ExponentiallyDecayingReservoir::new);
+ }
+
+ /**
+ * Constructs a custom application listener.
+ *
+ * @param metrics the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public InstrumentedResourceMethodApplicationListener(final MetricRegistry metrics, final Clock clock,
+ final boolean trackFilters,
+ final Supplier<Reservoir> reservoirSupplier) {
+ this.metrics = metrics;
+ this.clock = clock;
+ this.trackFilters = trackFilters;
+ this.reservoirSupplier = reservoirSupplier;
+ }
+
+ /**
+ * A private class to maintain the metric for a method annotated with the
+ * {@link ExceptionMetered} annotation, which needs to maintain both a meter
+ * and a cause for which the meter should be updated.
+ */
+ private static class ExceptionMeterMetric {
+ public final Meter meter;
+ public final Class<? extends Throwable> cause;
+
+ public ExceptionMeterMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final ExceptionMetered exceptionMetered) {
+ final String name = chooseName(exceptionMetered.name(),
+ exceptionMetered.absolute(), method, ExceptionMetered.DEFAULT_NAME_SUFFIX);
+ this.meter = registry.meter(name);
+ this.cause = exceptionMetered.cause();
+ }
+ }
+
+ /**
+ * A private class to maintain the metrics for a method annotated with the
+ * {@link ResponseMetered} annotation, which needs to maintain meters for
+ * different response codes
+ */
+ private static class ResponseMeterMetric {
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+ private final List<Meter> meters;
+ private final Map<Integer, Meter> responseCodeMeters;
+ private final MetricRegistry metricRegistry;
+ private final String metricName;
+ private final ResponseMeteredLevel level;
+
+ public ResponseMeterMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final ResponseMetered responseMetered) {
+ this.metricName = chooseName(responseMetered.name(), responseMetered.absolute(), method);
+ this.level = responseMetered.level();
+ this.meters = COARSE_METER_LEVELS.contains(level) ?
+ Collections.unmodifiableList(Arrays.asList(
+ registry.meter(name(metricName, "1xx-responses")), // 1xx
+ registry.meter(name(metricName, "2xx-responses")), // 2xx
+ registry.meter(name(metricName, "3xx-responses")), // 3xx
+ registry.meter(name(metricName, "4xx-responses")), // 4xx
+ registry.meter(name(metricName, "5xx-responses")) // 5xx
+ )) : Collections.emptyList();
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(level) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+ this.metricRegistry = registry;
+ }
+
+ public void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(level)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(level)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ meters.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(metricName, String.format("%d-responses", sc))));
+ }
+ }
+
+ private static class TimerRequestEventListener implements RequestEventListener {
+
+ private final ConcurrentMap<EventTypeAndMethod, Timer> timers;
+ private final Clock clock;
+ private final long start;
+ private Timer.Context resourceMethodStartContext;
+ private Timer.Context requestMatchedContext;
+ private Timer.Context responseFiltersStartContext;
+
+ public TimerRequestEventListener(final ConcurrentMap<EventTypeAndMethod, Timer> timers, final Clock clock) {
+ this.timers = timers;
+ this.clock = clock;
+ start = clock.getTick();
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ switch (event.getType()) {
+ case RESOURCE_METHOD_START:
+ resourceMethodStartContext = context(event);
+ break;
+ case REQUEST_MATCHED:
+ requestMatchedContext = context(event);
+ break;
+ case RESP_FILTERS_START:
+ responseFiltersStartContext = context(event);
+ break;
+ case RESOURCE_METHOD_FINISHED:
+ if (resourceMethodStartContext != null) {
+ resourceMethodStartContext.close();
+ }
+ break;
+ case REQUEST_FILTERED:
+ if (requestMatchedContext != null) {
+ requestMatchedContext.close();
+ }
+ break;
+ case RESP_FILTERS_FINISHED:
+ if (responseFiltersStartContext != null) {
+ responseFiltersStartContext.close();
+ }
+ break;
+ case FINISHED:
+ if (requestMatchedContext != null && responseFiltersStartContext != null) {
+ final Timer timer = timer(event);
+ if (timer != null) {
+ timer.update(clock.getTick() - start, TimeUnit.NANOSECONDS);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private Timer timer(RequestEvent event) {
+ final ResourceMethod resourceMethod = event.getUriInfo().getMatchedResourceMethod();
+ if (resourceMethod == null) {
+ return null;
+ }
+ return timers.get(new EventTypeAndMethod(event.getType(), resourceMethod.getInvocable().getDefinitionMethod()));
+ }
+
+ private Timer.Context context(RequestEvent event) {
+ final Timer timer = timer(event);
+ return timer != null ? timer.time() : null;
+ }
+ }
+
+ private static class MeterRequestEventListener implements RequestEventListener {
+ private final ConcurrentMap<Method, Meter> meters;
+
+ public MeterRequestEventListener(final ConcurrentMap<Method, Meter> meters) {
+ this.meters = meters;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ if (event.getType() == RequestEvent.Type.RESOURCE_METHOD_START) {
+ final Meter meter = this.meters.get(event.getUriInfo().getMatchedResourceMethod().getInvocable().getDefinitionMethod());
+ if (meter != null) {
+ meter.mark();
+ }
+ }
+ }
+ }
+
+ private static class ExceptionMeterRequestEventListener implements RequestEventListener {
+ private final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters;
+
+ public ExceptionMeterRequestEventListener(final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters) {
+ this.exceptionMeters = exceptionMeters;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ if (event.getType() == RequestEvent.Type.ON_EXCEPTION) {
+ final ResourceMethod method = event.getUriInfo().getMatchedResourceMethod();
+ final ExceptionMeterMetric metric = (method != null) ?
+ this.exceptionMeters.get(method.getInvocable().getDefinitionMethod()) : null;
+
+ if (metric != null) {
+ if (metric.cause.isAssignableFrom(event.getException().getClass()) ||
+ (event.getException().getCause() != null &&
+ metric.cause.isAssignableFrom(event.getException().getCause().getClass()))) {
+ metric.meter.mark();
+ }
+ }
+ }
+ }
+ }
+
+ private static class ResponseMeterRequestEventListener implements RequestEventListener {
+ private final ConcurrentMap<Method, ResponseMeterMetric> responseMeters;
+
+ public ResponseMeterRequestEventListener(final ConcurrentMap<Method, ResponseMeterMetric> responseMeters) {
+ this.responseMeters = responseMeters;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+ if (event.getType() == RequestEvent.Type.FINISHED) {
+ final ResourceMethod method = event.getUriInfo().getMatchedResourceMethod();
+ final ResponseMeterMetric metric = (method != null) ?
+ this.responseMeters.get(method.getInvocable().getDefinitionMethod()) : null;
+
+ if (metric != null) {
+ ContainerResponse containerResponse = event.getContainerResponse();
+ if (containerResponse == null && event.getException() != null) {
+ metric.mark(500);
+ } else if (containerResponse != null) {
+ metric.mark(containerResponse.getStatus());
+ }
+ }
+ }
+ }
+ }
+
+ private static class ChainedRequestEventListener implements RequestEventListener {
+ private final RequestEventListener[] listeners;
+
+ private ChainedRequestEventListener(final RequestEventListener... listeners) {
+ this.listeners = listeners;
+ }
+
+ @Override
+ public void onEvent(final RequestEvent event) {
+ for (RequestEventListener listener : listeners) {
+ listener.onEvent(event);
+ }
+ }
+ }
+
+ @Override
+ public void onEvent(ApplicationEvent event) {
+ if (event.getType() == ApplicationEvent.Type.INITIALIZATION_APP_FINISHED) {
+ registerMetricsForModel(event.getResourceModel());
+ }
+ }
+
+ @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);
+ final ResponseMetered classLevelResponseMetered = getClassLevelAnnotation(resource, ResponseMetered.class);
+
+ for (final ResourceMethod method : resource.getAllMethods()) {
+ registerTimedAnnotations(method, classLevelTimed);
+ registerMeteredAnnotations(method, classLevelMetered);
+ registerExceptionMeteredAnnotations(method, classLevelExceptionMetered);
+ registerResponseMeteredAnnotations(method, classLevelResponseMetered);
+ }
+
+ 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);
+ final ResponseMetered classLevelResponseMeteredChild = getClassLevelAnnotation(childResource, ResponseMetered.class);
+
+ for (final ResourceMethod method : childResource.getAllMethods()) {
+ registerTimedAnnotations(method, classLevelTimedChild);
+ registerMeteredAnnotations(method, classLevelMeteredChild);
+ registerExceptionMeteredAnnotations(method, classLevelExceptionMeteredChild);
+ registerResponseMeteredAnnotations(method, classLevelResponseMeteredChild);
+ }
+ }
+ }
+ }
+
+ @Override
+ public RequestEventListener onRequest(final RequestEvent event) {
+ final RequestEventListener listener = new ChainedRequestEventListener(
+ new TimerRequestEventListener(timers, clock),
+ new MeterRequestEventListener(meters),
+ new ExceptionMeterRequestEventListener(exceptionMeters),
+ new ResponseMeterRequestEventListener(responseMeters));
+
+ return listener;
+ }
+
+ 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) {
+ registerTimers(method, definitionMethod, classLevelTimed);
+ return;
+ }
+
+ final Timed annotation = definitionMethod.getAnnotation(Timed.class);
+ if (annotation != null) {
+ registerTimers(method, definitionMethod, annotation);
+ }
+ }
+
+ private void registerTimers(ResourceMethod method, Method definitionMethod, Timed annotation) {
+ timers.putIfAbsent(EventTypeAndMethod.requestMethodStart(definitionMethod), timerMetric(metrics, method, annotation));
+ if (trackFilters) {
+ timers.putIfAbsent(EventTypeAndMethod.requestMatched(definitionMethod), timerMetric(metrics, method, annotation, REQUEST_FILTERING));
+ timers.putIfAbsent(EventTypeAndMethod.respFiltersStart(definitionMethod), timerMetric(metrics, method, annotation, RESPONSE_FILTERING));
+ timers.putIfAbsent(EventTypeAndMethod.finished(definitionMethod), timerMetric(metrics, method, annotation, TOTAL));
+ }
+ }
+
+ 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) {
+ meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, annotation));
+ }
+ }
+
+ 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) {
+ exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, annotation));
+ }
+ }
+
+ private void registerResponseMeteredAnnotations(final ResourceMethod method, final ResponseMetered classLevelResponseMetered) {
+ final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+
+ if (classLevelResponseMetered != null) {
+ responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, classLevelResponseMetered));
+ return;
+ }
+ final ResponseMetered annotation = definitionMethod.getAnnotation(ResponseMetered.class);
+
+ if (annotation != null) {
+ responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, annotation));
+ }
+ }
+
+ private Timer timerMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final Timed timed,
+ final String... suffixes) {
+ final String name = chooseName(timed.name(), timed.absolute(), method, suffixes);
+ return registry.timer(name, () -> new Timer(reservoirSupplier.get(), clock));
+ }
+
+ private Meter meterMetric(final MetricRegistry registry,
+ final ResourceMethod method,
+ final Metered metered) {
+ final String name = chooseName(metered.name(), metered.absolute(), method);
+ return registry.meter(name, () -> new Meter(clock));
+ }
+
+ protected static String chooseName(final String explicitName, final boolean absolute, final ResourceMethod method,
+ final String... suffixes) {
+ final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+ final String metricName;
+ if (explicitName != null && !explicitName.isEmpty()) {
+ metricName = absolute ? explicitName : name(definitionMethod.getDeclaringClass(), explicitName);
+ } else {
+ metricName = name(definitionMethod.getDeclaringClass(), definitionMethod.getName());
+ }
+ return name(metricName, suffixes);
+ }
+
+ private static class EventTypeAndMethod {
+
+ private final RequestEvent.Type type;
+ private final Method method;
+
+ private EventTypeAndMethod(RequestEvent.Type type, Method method) {
+ this.type = type;
+ this.method = method;
+ }
+
+ static EventTypeAndMethod requestMethodStart(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.RESOURCE_METHOD_START, method);
+ }
+
+ static EventTypeAndMethod requestMatched(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.REQUEST_MATCHED, method);
+ }
+
+ static EventTypeAndMethod respFiltersStart(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.RESP_FILTERS_START, method);
+ }
+
+ static EventTypeAndMethod finished(Method method) {
+ return new EventTypeAndMethod(RequestEvent.Type.FINISHED, method);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EventTypeAndMethod that = (EventTypeAndMethod) o;
+
+ if (type != that.type) {
+ return false;
+ }
+ return method.equals(that.method);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type.hashCode();
+ result = 31 * result + method.hashCode();
+ return result;
+ }
+ }
+}
diff --git a/metrics-jersey31/src/main/java/io/dropwizard/metrics/jersey31/MetricsFeature.java b/metrics-jersey31/src/main/java/io/dropwizard/metrics/jersey31/MetricsFeature.java
new file mode 100644
index 0000000..87ae86e
--- /dev/null
+++ b/metrics-jersey31/src/main/java/io/dropwizard/metrics/jersey31/MetricsFeature.java
@@ -0,0 +1,97 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Reservoir;
+import com.codahale.metrics.SharedMetricRegistries;
+import jakarta.ws.rs.core.Feature;
+import jakarta.ws.rs.core.FeatureContext;
+
+import java.util.function.Supplier;
+
+/**
+ * A {@link Feature} which registers a {@link InstrumentedResourceMethodApplicationListener}
+ * for recording request events.
+ */
+public class MetricsFeature implements Feature {
+
+ private final MetricRegistry registry;
+ private final Clock clock;
+ private final boolean trackFilters;
+ private final Supplier<Reservoir> reservoirSupplier;
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ */
+ public MetricsFeature(MetricRegistry registry) {
+ this(registry, Clock.defaultClock());
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public MetricsFeature(MetricRegistry registry, Supplier<Reservoir> reservoirSupplier) {
+ this(registry, Clock.defaultClock(), false, reservoirSupplier);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock) {
+ this(registry, clock, false);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock, boolean trackFilters) {
+ this(registry, clock, trackFilters, ExponentiallyDecayingReservoir::new);
+ }
+
+ /*
+ * @param registry the metrics registry where the metrics will be stored
+ * @param clock the {@link Clock} to track time (used mostly in testing) in timers
+ * @param trackFilters whether the processing time for request and response filters should be tracked
+ * @param reservoirSupplier Supplier for creating the {@link Reservoir} for {@link Timer timers}.
+ */
+ public MetricsFeature(MetricRegistry registry, Clock clock, boolean trackFilters, Supplier<Reservoir> reservoirSupplier) {
+ this.registry = registry;
+ this.clock = clock;
+ this.trackFilters = trackFilters;
+ this.reservoirSupplier = reservoirSupplier;
+ }
+
+ public MetricsFeature(String registryName) {
+ this(SharedMetricRegistries.getOrCreate(registryName));
+ }
+
+ /**
+ * A call-back method called when the feature is to be enabled in a given
+ * runtime configuration scope.
+ * <p>
+ * The responsibility of the feature is to properly update the supplied runtime configuration context
+ * and return {@code true} if the feature was successfully enabled or {@code false} otherwise.
+ * <p>
+ * Note that under some circumstances the feature may decide not to enable itself, which
+ * is indicated by returning {@code false}. In such case the configuration context does
+ * not add the feature to the collection of enabled features and a subsequent call to
+ * {@link jakarta.ws.rs.core.Configuration#isEnabled(jakarta.ws.rs.core.Feature)} or
+ * {@link jakarta.ws.rs.core.Configuration#isEnabled(Class)} method
+ * would return {@code false}.
+ * <p>
+ *
+ * @param context configurable context in which the feature should be enabled.
+ * @return {@code true} if the feature was successfully enabled, {@code false}
+ * otherwise.
+ */
+ @Override
+ public boolean configure(FeatureContext context) {
+ context.register(new InstrumentedResourceMethodApplicationListener(registry, clock, trackFilters, reservoirSupplier));
+ return true;
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/CustomReservoirImplementationTest.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/CustomReservoirImplementationTest.java
new file mode 100644
index 0000000..60190d0
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/CustomReservoirImplementationTest.java
@@ -0,0 +1,44 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.UniformReservoir;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedResourceTimedPerClass;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+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;
+
+public class CustomReservoirImplementationTest extends JerseyTest {
+ static {
+ Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
+ }
+
+ private MetricRegistry registry;
+
+ @Override
+ protected Application configure() {
+ this.registry = new MetricRegistry();
+
+ return new ResourceConfig()
+ .register(new MetricsFeature(this.registry, UniformReservoir::new))
+ .register(InstrumentedResourceTimedPerClass.class);
+ }
+
+ @Test
+ public void timerHistogramIsUsingCustomReservoirImplementation() {
+ assertThat(target("timedPerClass").request().get(String.class)).isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedResourceTimedPerClass.class, "timedPerClass"));
+ assertThat(timer)
+ .extracting("histogram")
+ .extracting("reservoir")
+ .isInstanceOf(UniformReservoir.class);
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonFilterMetricsJerseyTest.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonFilterMetricsJerseyTest.java
new file mode 100644
index 0000000..e4bfc10
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonFilterMetricsJerseyTest.java
@@ -0,0 +1,162 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedFilteredResource;
+import io.dropwizard.metrics.jersey31.resources.TestRequestFilter;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Before;
+import org.junit.Test;
+
+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} with filter tracking
+ */
+public class SingletonFilterMetricsJerseyTest extends JerseyTest {
+ static {
+ Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
+ }
+
+ private MetricRegistry registry;
+
+ private TestClock testClock;
+
+ @Override
+ protected Application configure() {
+ registry = new MetricRegistry();
+ testClock = new TestClock();
+ ResourceConfig config = new ResourceConfig();
+ config = config.register(new MetricsFeature(this.registry, testClock, true));
+ config = config.register(new TestRequestFilter(testClock));
+ config = config.register(new InstrumentedFilteredResource(testClock));
+ return config;
+ }
+
+ @Before
+ public void resetClock() {
+ testClock.tick = 0;
+ }
+
+ @Test
+ public void timedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void explicitNamesAreTimed() {
+ assertThat(target("named")
+ .request()
+ .get(String.class))
+ .isEqualTo("fancy");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "fancyName"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void absoluteNamesAreTimed() {
+ assertThat(target("absolute")
+ .request()
+ .get(String.class))
+ .isEqualTo("absolute");
+
+ final Timer timer = registry.timer("absolutelyFancy");
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(1);
+ }
+
+ @Test
+ public void requestFiltersOfTimedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "request", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void responseFiltersOfTimedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "response", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void totalTimeOfTimedMethodsIsTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "timed", "total"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(5);
+ }
+
+ @Test
+ public void requestFiltersOfNamedMethodsAreTimed() {
+ assertThat(target("named")
+ .request()
+ .get(String.class))
+ .isEqualTo("fancy");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.class, "fancyName", "request", "filtering"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void requestFiltersOfAbsoluteMethodsAreTimed() {
+ assertThat(target("absolute")
+ .request()
+ .get(String.class))
+ .isEqualTo("absolute");
+
+ final Timer timer = registry.timer(name("absolutelyFancy", "request", "filtering"));
+ assertThat(timer.getCount()).isEqualTo(1);
+ assertThat(timer.getSnapshot().getValues()[0]).isEqualTo(4);
+ }
+
+ @Test
+ public void subResourcesFromLocatorsRegisterMetrics() {
+ assertThat(target("subresource/timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedFilteredResource.InstrumentedFilteredSubResource.class,
+ "timed"));
+ assertThat(timer.getCount()).isEqualTo(1);
+
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsExceptionMeteredPerClassJerseyTest.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsExceptionMeteredPerClassJerseyTest.java
new file mode 100644
index 0000000..d84c835
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsExceptionMeteredPerClassJerseyTest.java
@@ -0,0 +1,98 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedResourceExceptionMeteredPerClass;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedSubResourceExceptionMeteredPerClass;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import java.io.IOException;
+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;
+import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
+
+/**
+ * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
+ * in a Jersey {@link ResourceConfig}
+ */
+public class SingletonMetricsExceptionMeteredPerClassJerseyTest 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(InstrumentedResourceExceptionMeteredPerClass.class);
+
+ return config;
+ }
+
+ @Test
+ public void exceptionMeteredMethodsAreExceptionMetered() {
+ final Meter meter = registry.meter(name(InstrumentedResourceExceptionMeteredPerClass.class,
+ "exceptionMetered",
+ "exceptions"));
+
+ assertThat(target("exception-metered")
+ .request()
+ .get(String.class))
+ .isEqualTo("fuh");
+
+ assertThat(meter.getCount()).isZero();
+
+ try {
+ target("exception-metered")
+ .queryParam("splode", true)
+ .request()
+ .get(String.class);
+
+ failBecauseExceptionWasNotThrown(ProcessingException.class);
+ } catch (ProcessingException e) {
+ assertThat(e.getCause()).isInstanceOf(IOException.class);
+ }
+
+ assertThat(meter.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void subresourcesFromLocatorsRegisterMetrics() {
+ final Meter meter = registry.meter(name(InstrumentedSubResourceExceptionMeteredPerClass.class,
+ "exceptionMetered",
+ "exceptions"));
+
+ assertThat(target("subresource/exception-metered")
+ .request()
+ .get(String.class))
+ .isEqualTo("fuh");
+
+ assertThat(meter.getCount()).isZero();
+
+ try {
+ target("subresource/exception-metered")
+ .queryParam("splode", true)
+ .request()
+ .get(String.class);
+
+ failBecauseExceptionWasNotThrown(ProcessingException.class);
+ } catch (ProcessingException e) {
+ assertThat(e.getCause()).isInstanceOf(IOException.class);
+ }
+
+ assertThat(meter.getCount()).isEqualTo(1);
+ }
+
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsJerseyTest.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsJerseyTest.java
new file mode 100644
index 0000000..e96fd56
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsJerseyTest.java
@@ -0,0 +1,191 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedResource;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedSubResource;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import org.glassfish.jersey.client.ClientResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import java.io.IOException;
+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;
+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);
+ }
+
+ 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(InstrumentedResource.class);
+
+ return config;
+ }
+
+ @Test
+ public void timedMethodsAreTimed() {
+ assertThat(target("timed")
+ .request()
+ .get(String.class))
+ .isEqualTo("yay");
+
+ final Timer timer = registry.timer(name(InstrumentedResource.class, "timed"));
+
+ assertThat(timer.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void meteredMethodsAreMetered() {
+ assertThat(target("metered")
+ .request()
+ .get(String.class))
+ .isEqualTo("woo");
+
+ 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,
+ "exceptionMetered",
+ "exceptions"));
+
+ assertThat(target("exception-metered")
+ .request()
+ .get(String.class))
+ .isEqualTo("fuh");
+
+ assertThat(meter.getCount()).isZero();
+
+ try {
+ target("exception-metered")
+ .queryParam("splode", true)
+ .request()
+ .get(String.class);
+
+ failBecauseExceptionWasNotThrown(ProcessingException.class);
+ } catch (ProcessingException e) {
+ assertThat(e.getCause()).isInstanceOf(IOException.class);
+ }
+
+ assertThat(meter.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMeteredMethodsAreMeteredWithCoarseLevel() {
+ final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredCoarse",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredCoarse",
+ "200-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(target("response-metered-coarse")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ assertThat(meter2xx.getCount()).isOne();
+ assertThat(meter200.getCount()).isZero();
+ }
+
+ @Test
+ public void responseMeteredMethodsAreMeteredWithDetailedLevel() {
+ final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "200-responses"));
+ final Meter meter201 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredDetailed",
+ "201-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(meter201.getCount()).isZero();
+ assertThat(target("response-metered-detailed")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+ assertThat(target("response-metered-detailed")
+ .queryParam("status_code", 201)
+ .request()
+ .get().getStatus())
+ .isEqualTo(201);
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isOne();
+ assertThat(meter201.getCount()).isOne();
+ }
+
+ @Test
+ public void responseMeteredMethodsAreMeteredWithAllLevel() {
+ final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredAll",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedResource.class,
+ "responseMeteredAll",
+ "200-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(target("response-metered-all")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ assertThat(meter2xx.getCount()).isOne();
+ assertThat(meter200.getCount()).isOne();
+ }
+
+ @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");
+ }
+ }
+
+ @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-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsMeteredPerClassJerseyTest.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsMeteredPerClassJerseyTest.java
new file mode 100644
index 0000000..d3e89de
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsMeteredPerClassJerseyTest.java
@@ -0,0 +1,66 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedResourceMeteredPerClass;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedSubResourceMeteredPerClass;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+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-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsResponseMeteredPerClassJerseyTest.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsResponseMeteredPerClassJerseyTest.java
new file mode 100644
index 0000000..4aa3877
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsResponseMeteredPerClassJerseyTest.java
@@ -0,0 +1,146 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import io.dropwizard.metrics.jersey31.exception.mapper.TestExceptionMapper;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedResourceResponseMeteredPerClass;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedSubResourceResponseMeteredPerClass;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+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;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
+ * in a Jersey {@link ResourceConfig}
+ */
+public class SingletonMetricsResponseMeteredPerClassJerseyTest 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(InstrumentedResourceResponseMeteredPerClass.class);
+ config = config.register(new TestExceptionMapper());
+
+ return config;
+ }
+
+ @Test
+ public void responseMetered2xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered2xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ final Meter meter2xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered2xxPerClass",
+ "2xx-responses"));
+
+ assertThat(meter2xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMetered4xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered4xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(400);
+ assertThat(target("responseMeteredBadRequestPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(400);
+
+ final Meter meter4xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered4xxPerClass",
+ "4xx-responses"));
+ final Meter meterException4xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredBadRequestPerClass",
+ "4xx-responses"));
+
+ assertThat(meter4xx.getCount()).isEqualTo(1);
+ assertThat(meterException4xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMetered5xxPerClassMethodsAreMetered() {
+ assertThat(target("responseMetered5xxPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(500);
+
+ final Meter meter5xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMetered5xxPerClass",
+ "5xx-responses"));
+
+ assertThat(meter5xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMeteredMappedExceptionPerClassMethodsAreMetered() {
+ assertThat(target("responseMeteredTestExceptionPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(500);
+
+ final Meter meterTestException = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredTestExceptionPerClass",
+ "5xx-responses"));
+
+ assertThat(meterTestException.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void responseMeteredUnmappedExceptionPerClassMethodsAreMetered() {
+ try {
+ target("responseMeteredRuntimeExceptionPerClass")
+ .request()
+ .get();
+ fail("expected RuntimeException");
+ } catch (Exception e) {
+ assertThat(e.getCause()).isInstanceOf(RuntimeException.class);
+ }
+
+ final Meter meterException5xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
+ "responseMeteredRuntimeExceptionPerClass",
+ "5xx-responses"));
+
+ assertThat(meterException5xx.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void subresourcesFromLocatorsRegisterMetrics() {
+ final Meter meter2xx = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
+ "responseMeteredPerClass",
+ "2xx-responses"));
+ final Meter meter200 = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
+ "responseMeteredPerClass",
+ "200-responses"));
+
+ assertThat(meter2xx.getCount()).isZero();
+ assertThat(meter200.getCount()).isZero();
+ assertThat(target("subresource/responseMeteredPerClass")
+ .request()
+ .get().getStatus())
+ .isEqualTo(200);
+
+ assertThat(meter2xx.getCount()).isOne();
+ assertThat(meter200.getCount()).isOne();
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsTimedPerClassJerseyTest.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsTimedPerClassJerseyTest.java
new file mode 100644
index 0000000..a1e39ee
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/SingletonMetricsTimedPerClassJerseyTest.java
@@ -0,0 +1,66 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedResourceTimedPerClass;
+import io.dropwizard.metrics.jersey31.resources.InstrumentedSubResourceTimedPerClass;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+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-jersey31/src/test/java/io/dropwizard/metrics/jersey31/TestClock.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/TestClock.java
new file mode 100644
index 0000000..b9d34e5
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/TestClock.java
@@ -0,0 +1,13 @@
+package io.dropwizard.metrics.jersey31;
+
+import com.codahale.metrics.Clock;
+
+public class TestClock extends Clock {
+
+ public long tick;
+
+ @Override
+ public long getTick() {
+ return tick;
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/exception/TestException.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/exception/TestException.java
new file mode 100644
index 0000000..1bf2427
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/exception/TestException.java
@@ -0,0 +1,9 @@
+package io.dropwizard.metrics.jersey31.exception;
+
+public class TestException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public TestException(String message) {
+ super(message);
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/exception/mapper/TestExceptionMapper.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/exception/mapper/TestExceptionMapper.java
new file mode 100644
index 0000000..a3ecece
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/exception/mapper/TestExceptionMapper.java
@@ -0,0 +1,14 @@
+package io.dropwizard.metrics.jersey31.exception.mapper;
+
+import io.dropwizard.metrics.jersey31.exception.TestException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+@Provider
+public class TestExceptionMapper implements ExceptionMapper<TestException> {
+ @Override
+ public Response toResponse(TestException exception) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedFilteredResource.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedFilteredResource.java
new file mode 100644
index 0000000..6146c5f
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedFilteredResource.java
@@ -0,0 +1,61 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import io.dropwizard.metrics.jersey31.TestClock;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedFilteredResource {
+
+ private final TestClock testClock;
+
+ public InstrumentedFilteredResource(TestClock testClock) {
+ this.testClock = testClock;
+ }
+
+ @GET
+ @Timed
+ @Path("/timed")
+ public String timed() {
+ testClock.tick++;
+ return "yay";
+ }
+
+ @GET
+ @Timed(name = "fancyName")
+ @Path("/named")
+ public String named() {
+ testClock.tick++;
+ return "fancy";
+ }
+
+ @GET
+ @Timed(name = "absolutelyFancy", absolute = true)
+ @Path("/absolute")
+ public String absolute() {
+ testClock.tick++;
+ return "absolute";
+ }
+
+ @Path("/subresource")
+ public InstrumentedFilteredSubResource locateSubResource() {
+ return new InstrumentedFilteredSubResource();
+ }
+
+ @Produces(MediaType.TEXT_PLAIN)
+ public class InstrumentedFilteredSubResource {
+
+ @GET
+ @Timed
+ @Path("/timed")
+ public String timed() {
+ testClock.tick += 2;
+ return "yay";
+ }
+
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResource.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResource.java
new file mode 100644
index 0000000..60e3efb
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResource.java
@@ -0,0 +1,73 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.ExceptionMetered;
+import com.codahale.metrics.annotation.Metered;
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import java.io.IOException;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+@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";
+ }
+
+ @GET
+ @ExceptionMetered(cause = IOException.class)
+ @Path("/exception-metered")
+ public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
+ if (splode) {
+ throw new IOException("AUGH");
+ }
+ return "fuh";
+ }
+
+ @GET
+ @ResponseMetered(level = DETAILED)
+ @Path("/response-metered-detailed")
+ public Response responseMeteredDetailed(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
+ @GET
+ @ResponseMetered(level = COARSE)
+ @Path("/response-metered-coarse")
+ public Response responseMeteredCoarse(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
+ @GET
+ @ResponseMetered(level = ALL)
+ @Path("/response-metered-all")
+ public Response responseMeteredAll(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
+ return Response.status(Response.Status.fromStatusCode(statusCode)).build();
+ }
+
+ @Path("/subresource")
+ public InstrumentedSubResource locateSubResource() {
+ return new InstrumentedSubResource();
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceExceptionMeteredPerClass.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceExceptionMeteredPerClass.java
new file mode 100644
index 0000000..449c777
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceExceptionMeteredPerClass.java
@@ -0,0 +1,32 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.ExceptionMetered;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+
+import java.io.IOException;
+
+@ExceptionMetered(cause = IOException.class)
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedResourceExceptionMeteredPerClass {
+
+ @GET
+ @Path("/exception-metered")
+ public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
+ if (splode) {
+ throw new IOException("AUGH");
+ }
+ return "fuh";
+ }
+
+ @Path("/subresource")
+ public InstrumentedSubResourceExceptionMeteredPerClass locateSubResource() {
+ return new InstrumentedSubResourceExceptionMeteredPerClass();
+ }
+
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceMeteredPerClass.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceMeteredPerClass.java
new file mode 100644
index 0000000..f9f6804
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceMeteredPerClass.java
@@ -0,0 +1,25 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.Metered;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Metered
+@Path("/")
+@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-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceResponseMeteredPerClass.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceResponseMeteredPerClass.java
new file mode 100644
index 0000000..8c10ba1
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceResponseMeteredPerClass.java
@@ -0,0 +1,58 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import io.dropwizard.metrics.jersey31.exception.TestException;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+@ResponseMetered
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedResourceResponseMeteredPerClass {
+
+ @GET
+ @Path("/responseMetered2xxPerClass")
+ public Response responseMetered2xxPerClass() {
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/responseMetered4xxPerClass")
+ public Response responseMetered4xxPerClass() {
+ return Response.status(Response.Status.BAD_REQUEST).build();
+ }
+
+ @GET
+ @Path("/responseMetered5xxPerClass")
+ public Response responseMetered5xxPerClass() {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ @GET
+ @Path("/responseMeteredBadRequestPerClass")
+ public String responseMeteredBadRequestPerClass() {
+ throw new BadRequestException();
+ }
+
+ @GET
+ @Path("/responseMeteredRuntimeExceptionPerClass")
+ public String responseMeteredRuntimeExceptionPerClass() {
+ throw new RuntimeException();
+ }
+
+ @GET
+ @Path("/responseMeteredTestExceptionPerClass")
+ public String responseMeteredTestExceptionPerClass() {
+ throw new TestException("test");
+ }
+
+ @Path("/subresource")
+ public InstrumentedSubResourceResponseMeteredPerClass locateSubResource() {
+ return new InstrumentedSubResourceResponseMeteredPerClass();
+ }
+
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceTimedPerClass.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceTimedPerClass.java
new file mode 100644
index 0000000..fb45f28
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedResourceTimedPerClass.java
@@ -0,0 +1,25 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Timed
+@Path("/")
+@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-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResource.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResource.java
new file mode 100644
index 0000000..36c8e6a
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResource.java
@@ -0,0 +1,19 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResource {
+
+ @GET
+ @Timed
+ @Path("/timed")
+ public String timed() {
+ return "yay";
+ }
+
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceExceptionMeteredPerClass.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
new file mode 100644
index 0000000..e983ade
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
@@ -0,0 +1,24 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.ExceptionMetered;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+
+import java.io.IOException;
+
+@ExceptionMetered(cause = IOException.class)
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceExceptionMeteredPerClass {
+ @GET
+ @Path("/exception-metered")
+ public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
+ if (splode) {
+ throw new IOException("AUGH");
+ }
+ return "fuh";
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceMeteredPerClass.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceMeteredPerClass.java
new file mode 100644
index 0000000..5282641
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceMeteredPerClass.java
@@ -0,0 +1,17 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.Metered;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Metered
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceMeteredPerClass {
+ @GET
+ @Path("/meteredPerClass")
+ public String meteredPerClass() {
+ return "yay";
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceResponseMeteredPerClass.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceResponseMeteredPerClass.java
new file mode 100644
index 0000000..cf42959
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceResponseMeteredPerClass.java
@@ -0,0 +1,20 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+
+@ResponseMetered(level = ALL)
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceResponseMeteredPerClass {
+ @GET
+ @Path("/responseMeteredPerClass")
+ public Response responseMeteredPerClass() {
+ return Response.status(Response.Status.OK).build();
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceTimedPerClass.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceTimedPerClass.java
new file mode 100644
index 0000000..0c115f2
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/InstrumentedSubResourceTimedPerClass.java
@@ -0,0 +1,17 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Timed
+@Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceTimedPerClass {
+ @GET
+ @Path("/timedPerClass")
+ public String timedPerClass() {
+ return "yay";
+ }
+}
diff --git a/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/TestRequestFilter.java b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/TestRequestFilter.java
new file mode 100644
index 0000000..9ceb8ec
--- /dev/null
+++ b/metrics-jersey31/src/test/java/io/dropwizard/metrics/jersey31/resources/TestRequestFilter.java
@@ -0,0 +1,21 @@
+package io.dropwizard.metrics.jersey31.resources;
+
+import io.dropwizard.metrics.jersey31.TestClock;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+
+import java.io.IOException;
+
+public class TestRequestFilter implements ContainerRequestFilter {
+
+ private final TestClock testClock;
+
+ public TestRequestFilter(TestClock testClock) {
+ this.testClock = testClock;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext containerRequestContext) throws IOException {
+ testClock.tick += 4;
+ }
+}
diff --git a/metrics-jetty10/pom.xml b/metrics-jetty10/pom.xml
new file mode 100644
index 0000000..b05136d
--- /dev/null
+++ b/metrics-jetty10/pom.xml
@@ -0,0 +1,123 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jetty10</artifactId>
+ <name>Metrics Integration for Jetty 10.x and higher</name>
+ <packaging>bundle</packaging>
+ <description>
+ A set of extensions for Jetty 10.x and higher which provide instrumentation of thread pools, connector
+ metrics, and application latency and utilization.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.jetty10</javaModuleName>
+
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+
+ <slf4j.version>2.0.11</slf4j.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-bom</artifactId>
+ <version>${jetty10.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-servlet-api</artifactId>
+ <version>4.0.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactory.java b/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedConnectionFactory.java
similarity index 65%
rename from metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactory.java
rename to metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedConnectionFactory.java
index 02d3f8c..481abb5 100644
--- a/metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactory.java
+++ b/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedConnectionFactory.java
@@ -1,5 +1,6 @@
-package com.codahale.metrics.jetty9;
+package io.dropwizard.metrics.jetty10;
+import com.codahale.metrics.Counter;
import com.codahale.metrics.Timer;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
@@ -7,13 +8,21 @@ import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import java.util.List;
+
public class InstrumentedConnectionFactory extends ContainerLifeCycle implements ConnectionFactory {
private final ConnectionFactory connectionFactory;
private final Timer timer;
+ private final Counter counter;
public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer) {
+ this(connectionFactory, timer, null);
+ }
+
+ public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer, Counter counter) {
this.connectionFactory = connectionFactory;
this.timer = timer;
+ this.counter = counter;
addBean(connectionFactory);
}
@@ -22,20 +31,31 @@ public class InstrumentedConnectionFactory extends ContainerLifeCycle implements
return connectionFactory.getProtocol();
}
+ @Override
+ public List<String> getProtocols() {
+ return connectionFactory.getProtocols();
+ }
+
@Override
public Connection newConnection(Connector connector, EndPoint endPoint) {
final Connection connection = connectionFactory.newConnection(connector, endPoint);
- connection.addListener(new Connection.Listener() {
+ connection.addEventListener(new Connection.Listener() {
private Timer.Context context;
@Override
public void onOpened(Connection connection) {
this.context = timer.time();
+ if (counter != null) {
+ counter.inc();
+ }
}
@Override
public void onClosed(Connection connection) {
context.stop();
+ if (counter != null) {
+ counter.dec();
+ }
}
});
return connection;
diff --git a/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedHandler.java b/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedHandler.java
new file mode 100644
index 0000000..bb849e0
--- /dev/null
+++ b/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedHandler.java
@@ -0,0 +1,444 @@
+package io.dropwizard.metrics.jetty10;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.AsyncContextState;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+/**
+ * A Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
+ * instance.
+ */
+public class InstrumentedHandler extends HandlerWrapper {
+ private static final String NAME_REQUESTS = "requests";
+ private static final String NAME_DISPATCHES = "dispatches";
+ private static final String NAME_ACTIVE_REQUESTS = "active-requests";
+ private static final String NAME_ACTIVE_DISPATCHES = "active-dispatches";
+ private static final String NAME_ACTIVE_SUSPENDED = "active-suspended";
+ private static final String NAME_ASYNC_DISPATCHES = "async-dispatches";
+ private static final String NAME_ASYNC_TIMEOUTS = "async-timeouts";
+ private static final String NAME_1XX_RESPONSES = "1xx-responses";
+ private static final String NAME_2XX_RESPONSES = "2xx-responses";
+ private static final String NAME_3XX_RESPONSES = "3xx-responses";
+ private static final String NAME_4XX_RESPONSES = "4xx-responses";
+ private static final String NAME_5XX_RESPONSES = "5xx-responses";
+ private static final String NAME_GET_REQUESTS = "get-requests";
+ private static final String NAME_POST_REQUESTS = "post-requests";
+ private static final String NAME_HEAD_REQUESTS = "head-requests";
+ private static final String NAME_PUT_REQUESTS = "put-requests";
+ private static final String NAME_DELETE_REQUESTS = "delete-requests";
+ private static final String NAME_OPTIONS_REQUESTS = "options-requests";
+ private static final String NAME_TRACE_REQUESTS = "trace-requests";
+ private static final String NAME_CONNECT_REQUESTS = "connect-requests";
+ private static final String NAME_MOVE_REQUESTS = "move-requests";
+ private static final String NAME_OTHER_REQUESTS = "other-requests";
+ private static final String NAME_PERCENT_4XX_1M = "percent-4xx-1m";
+ private static final String NAME_PERCENT_4XX_5M = "percent-4xx-5m";
+ private static final String NAME_PERCENT_4XX_15M = "percent-4xx-15m";
+ private static final String NAME_PERCENT_5XX_1M = "percent-5xx-1m";
+ private static final String NAME_PERCENT_5XX_5M = "percent-5xx-5m";
+ private static final String NAME_PERCENT_5XX_15M = "percent-5xx-15m";
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+
+ private final MetricRegistry metricRegistry;
+
+ private String name;
+ private final String prefix;
+
+ // the requests handled by this handler, excluding active
+ private Timer requests;
+
+ // the number of dispatches seen by this handler, excluding active
+ private Timer dispatches;
+
+ // the number of active requests
+ private Counter activeRequests;
+
+ // the number of active dispatches
+ private Counter activeDispatches;
+
+ // the number of requests currently suspended.
+ private Counter activeSuspended;
+
+ // the number of requests that have been asynchronously dispatched
+ private Meter asyncDispatches;
+
+ // the number of requests that expired while suspended
+ private Meter asyncTimeouts;
+
+ private final ResponseMeteredLevel responseMeteredLevel;
+ private List<Meter> responses;
+ private Map<Integer, Meter> responseCodeMeters;
+
+ private Timer getRequests;
+ private Timer postRequests;
+ private Timer headRequests;
+ private Timer putRequests;
+ private Timer deleteRequests;
+ private Timer optionsRequests;
+ private Timer traceRequests;
+ private Timer connectRequests;
+ private Timer moveRequests;
+ private Timer otherRequests;
+
+ private AsyncListener listener;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ public InstrumentedHandler(MetricRegistry registry) {
+ this(registry, null);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ */
+ public InstrumentedHandler(MetricRegistry registry, String prefix) {
+ this(registry, prefix, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ * @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
+ */
+ public InstrumentedHandler(MetricRegistry registry, String prefix, ResponseMeteredLevel responseMeteredLevel) {
+ this.metricRegistry = registry;
+ this.prefix = prefix;
+ this.responseMeteredLevel = responseMeteredLevel;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ final String prefix = getMetricPrefix();
+
+ this.requests = metricRegistry.timer(name(prefix, NAME_REQUESTS));
+ this.dispatches = metricRegistry.timer(name(prefix, NAME_DISPATCHES));
+
+ this.activeRequests = metricRegistry.counter(name(prefix, NAME_ACTIVE_REQUESTS));
+ this.activeDispatches = metricRegistry.counter(name(prefix, NAME_ACTIVE_DISPATCHES));
+ this.activeSuspended = metricRegistry.counter(name(prefix, NAME_ACTIVE_SUSPENDED));
+
+ this.asyncDispatches = metricRegistry.meter(name(prefix, NAME_ASYNC_DISPATCHES));
+ this.asyncTimeouts = metricRegistry.meter(name(prefix, NAME_ASYNC_TIMEOUTS));
+
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(responseMeteredLevel) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+
+ this.getRequests = metricRegistry.timer(name(prefix, NAME_GET_REQUESTS));
+ this.postRequests = metricRegistry.timer(name(prefix, NAME_POST_REQUESTS));
+ this.headRequests = metricRegistry.timer(name(prefix, NAME_HEAD_REQUESTS));
+ this.putRequests = metricRegistry.timer(name(prefix, NAME_PUT_REQUESTS));
+ this.deleteRequests = metricRegistry.timer(name(prefix, NAME_DELETE_REQUESTS));
+ this.optionsRequests = metricRegistry.timer(name(prefix, NAME_OPTIONS_REQUESTS));
+ this.traceRequests = metricRegistry.timer(name(prefix, NAME_TRACE_REQUESTS));
+ this.connectRequests = metricRegistry.timer(name(prefix, NAME_CONNECT_REQUESTS));
+ this.moveRequests = metricRegistry.timer(name(prefix, NAME_MOVE_REQUESTS));
+ this.otherRequests = metricRegistry.timer(name(prefix, NAME_OTHER_REQUESTS));
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ this.responses = Collections.unmodifiableList(Arrays.asList(
+ metricRegistry.meter(name(prefix, NAME_1XX_RESPONSES)), // 1xx
+ metricRegistry.meter(name(prefix, NAME_2XX_RESPONSES)), // 2xx
+ metricRegistry.meter(name(prefix, NAME_3XX_RESPONSES)), // 3xx
+ metricRegistry.meter(name(prefix, NAME_4XX_RESPONSES)), // 4xx
+ metricRegistry.meter(name(prefix, NAME_5XX_RESPONSES)) // 5xx
+ ));
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_15M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_15M), new RatioGauge() {
+ @Override
+ public Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+ } else {
+ this.responses = Collections.emptyList();
+ }
+
+
+ this.listener = new AsyncAttachingListener();
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_SUSPENDED));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_TIMEOUTS));
+ metricRegistry.remove(name(prefix, NAME_1XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_2XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_3XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_4XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_5XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_GET_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_POST_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_HEAD_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PUT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DELETE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OPTIONS_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_TRACE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_CONNECT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_MOVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OTHER_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_15M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_15M));
+
+ if (responseCodeMeters != null) {
+ responseCodeMeters.keySet().stream()
+ .map(sc -> name(getMetricPrefix(), String.format("%d-responses", sc)))
+ .forEach(metricRegistry::remove);
+ }
+ super.doStop();
+ }
+
+ @Override
+ public void handle(String path,
+ Request request,
+ HttpServletRequest httpRequest,
+ HttpServletResponse httpResponse) throws IOException, ServletException {
+
+ activeDispatches.inc();
+
+ final long start;
+ final HttpChannelState state = request.getHttpChannelState();
+ if (state.isInitial()) {
+ // new request
+ activeRequests.inc();
+ start = request.getTimeStamp();
+ state.addListener(listener);
+ } else {
+ // resumed request
+ start = System.currentTimeMillis();
+ activeSuspended.dec();
+ if (state.getState() == HttpChannelState.State.HANDLING) {
+ asyncDispatches.mark();
+ }
+ }
+
+ try {
+ super.handle(path, request, httpRequest, httpResponse);
+ } finally {
+ final long now = System.currentTimeMillis();
+ final long dispatched = now - start;
+
+ activeDispatches.dec();
+ dispatches.update(dispatched, TimeUnit.MILLISECONDS);
+
+ if (state.isSuspended()) {
+ activeSuspended.inc();
+ } else if (state.isInitial()) {
+ updateResponses(httpRequest, httpResponse, start, request.isHandled());
+ }
+ // else onCompletion will handle it.
+ }
+ }
+
+ private Timer requestTimer(String method) {
+ final HttpMethod m = HttpMethod.fromString(method);
+ if (m == null) {
+ return otherRequests;
+ } else {
+ switch (m) {
+ case GET:
+ return getRequests;
+ case POST:
+ return postRequests;
+ case PUT:
+ return putRequests;
+ case HEAD:
+ return headRequests;
+ case DELETE:
+ return deleteRequests;
+ case OPTIONS:
+ return optionsRequests;
+ case TRACE:
+ return traceRequests;
+ case CONNECT:
+ return connectRequests;
+ case MOVE:
+ return moveRequests;
+ default:
+ return otherRequests;
+ }
+ }
+ }
+
+ private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start, boolean isHandled) {
+ if (isHandled) {
+ mark(response.getStatus());
+ } else {
+ mark(404);; // will end up with a 404 response sent by HttpChannel.handle
+ }
+ activeRequests.dec();
+ final long elapsedTime = System.currentTimeMillis() - start;
+ requests.update(elapsedTime, TimeUnit.MILLISECONDS);
+ requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
+ }
+
+ private void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(responseMeteredLevel)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ responses.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(getMetricPrefix(), String.format("%d-responses", sc))));
+ }
+
+ private String getMetricPrefix() {
+ return this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name);
+ }
+
+ private class AsyncAttachingListener implements AsyncListener {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ event.getAsyncContext().addListener(new InstrumentedAsyncListener());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {}
+ };
+
+ private class InstrumentedAsyncListener implements AsyncListener {
+ private final long startTime;
+
+ InstrumentedAsyncListener() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {
+ asyncTimeouts.mark();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {
+ final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
+ final HttpServletRequest request = (HttpServletRequest) state.getRequest();
+ final HttpServletResponse response = (HttpServletResponse) state.getResponse();
+ updateResponses(request, response, startTime, true);
+ if (!state.getHttpChannelState().isSuspended()) {
+ activeSuspended.dec();
+ }
+ }
+ }
+}
diff --git a/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedHttpChannelListener.java b/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedHttpChannelListener.java
new file mode 100644
index 0000000..decb8bb
--- /dev/null
+++ b/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedHttpChannelListener.java
@@ -0,0 +1,424 @@
+package io.dropwizard.metrics.jetty10;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.AsyncContextState;
+import org.eclipse.jetty.server.HttpChannel.Listener;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+/**
+ * A Jetty {@link org.eclipse.jetty.server.HttpChannel.Listener} implementation which records various metrics about
+ * underlying channel instance. Unlike {@link InstrumentedHandler} that uses internal API, this class should be
+ * future proof. To install it, just add instance of this class to {@link org.eclipse.jetty.server.Connector} as bean.
+ *
+ * @since TBD
+ */
+public class InstrumentedHttpChannelListener
+ implements Listener {
+ private static final String START_ATTR = InstrumentedHttpChannelListener.class.getName() + ".start";
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+
+ private final MetricRegistry metricRegistry;
+
+ // the requests handled by this handler, excluding active
+ private final Timer requests;
+
+ // the number of dispatches seen by this handler, excluding active
+ private final Timer dispatches;
+
+ // the number of active requests
+ private final Counter activeRequests;
+
+ // the number of active dispatches
+ private final Counter activeDispatches;
+
+ // the number of requests currently suspended.
+ private final Counter activeSuspended;
+
+ // the number of requests that have been asynchronously dispatched
+ private final Meter asyncDispatches;
+
+ // the number of requests that expired while suspended
+ private final Meter asyncTimeouts;
+
+ private final ResponseMeteredLevel responseMeteredLevel;
+ private final List<Meter> responses;
+ private final Map<Integer, Meter> responseCodeMeters;
+ private final String prefix;
+ private final Timer getRequests;
+ private final Timer postRequests;
+ private final Timer headRequests;
+ private final Timer putRequests;
+ private final Timer deleteRequests;
+ private final Timer optionsRequests;
+ private final Timer traceRequests;
+ private final Timer connectRequests;
+ private final Timer moveRequests;
+ private final Timer otherRequests;
+
+ private final AsyncListener listener;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry) {
+ this(registry, null, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param pref the prefix to use for the metrics names
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry, String pref) {
+ this(registry, pref, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param pref the prefix to use for the metrics names
+ * @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry, String pref, ResponseMeteredLevel responseMeteredLevel) {
+ this.metricRegistry = registry;
+
+ this.prefix = (pref == null) ? getClass().getName() : pref;
+
+ this.requests = metricRegistry.timer(name(prefix, "requests"));
+ this.dispatches = metricRegistry.timer(name(prefix, "dispatches"));
+
+ this.activeRequests = metricRegistry.counter(name(prefix, "active-requests"));
+ this.activeDispatches = metricRegistry.counter(name(prefix, "active-dispatches"));
+ this.activeSuspended = metricRegistry.counter(name(prefix, "active-suspended"));
+
+ this.asyncDispatches = metricRegistry.meter(name(prefix, "async-dispatches"));
+ this.asyncTimeouts = metricRegistry.meter(name(prefix, "async-timeouts"));
+
+ this.responseMeteredLevel = responseMeteredLevel;
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(responseMeteredLevel) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+ this.responses = COARSE_METER_LEVELS.contains(responseMeteredLevel) ?
+ Collections.unmodifiableList(Arrays.asList(
+ registry.meter(name(prefix, "1xx-responses")), // 1xx
+ registry.meter(name(prefix, "2xx-responses")), // 2xx
+ registry.meter(name(prefix, "3xx-responses")), // 3xx
+ registry.meter(name(prefix, "4xx-responses")), // 4xx
+ registry.meter(name(prefix, "5xx-responses")) // 5xx
+ )) : Collections.emptyList();
+
+ this.getRequests = metricRegistry.timer(name(prefix, "get-requests"));
+ this.postRequests = metricRegistry.timer(name(prefix, "post-requests"));
+ this.headRequests = metricRegistry.timer(name(prefix, "head-requests"));
+ this.putRequests = metricRegistry.timer(name(prefix, "put-requests"));
+ this.deleteRequests = metricRegistry.timer(name(prefix, "delete-requests"));
+ this.optionsRequests = metricRegistry.timer(name(prefix, "options-requests"));
+ this.traceRequests = metricRegistry.timer(name(prefix, "trace-requests"));
+ this.connectRequests = metricRegistry.timer(name(prefix, "connect-requests"));
+ this.moveRequests = metricRegistry.timer(name(prefix, "move-requests"));
+ this.otherRequests = metricRegistry.timer(name(prefix, "other-requests"));
+
+ metricRegistry.register(name(prefix, "percent-4xx-1m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-4xx-5m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-4xx-15m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-1m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-5m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-15m"), new RatioGauge() {
+ @Override
+ public Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ this.listener = new AsyncAttachingListener();
+ }
+
+ @Override
+ public void onRequestBegin(final Request request) {
+
+ }
+
+ @Override
+ public void onBeforeDispatch(final Request request) {
+ before(request);
+ }
+
+ @Override
+ public void onDispatchFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onAfterDispatch(final Request request) {
+ after(request);
+ }
+
+ @Override
+ public void onRequestContent(final Request request, final ByteBuffer content) {
+
+ }
+
+ @Override
+ public void onRequestContentEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestTrailers(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onResponseBegin(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseCommit(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseContent(final Request request, final ByteBuffer content) {
+
+ }
+
+ @Override
+ public void onResponseEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onComplete(final Request request) {
+
+ }
+
+ private void before(final Request request) {
+ activeDispatches.inc();
+
+ final long start;
+ final HttpChannelState state = request.getHttpChannelState();
+ if (state.isInitial()) {
+ // new request
+ activeRequests.inc();
+ start = request.getTimeStamp();
+ state.addListener(listener);
+ } else {
+ // resumed request
+ start = System.currentTimeMillis();
+ activeSuspended.dec();
+ if (state.isAsyncStarted()) {
+ asyncDispatches.mark();
+ }
+ }
+ request.setAttribute(START_ATTR, start);
+ }
+
+ private void after(final Request request) {
+ final long start = (long) request.getAttribute(START_ATTR);
+ final long now = System.currentTimeMillis();
+ final long dispatched = now - start;
+
+ activeDispatches.dec();
+ dispatches.update(dispatched, TimeUnit.MILLISECONDS);
+
+ final HttpChannelState state = request.getHttpChannelState();
+ if (state.isSuspended()) {
+ activeSuspended.inc();
+ } else if (state.isInitial()) {
+ updateResponses(request, request.getResponse(), start, request.isHandled());
+ }
+ // else onCompletion will handle it.
+ }
+
+ private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start, boolean isHandled) {
+ if (isHandled) {
+ mark(response.getStatus());
+ } else {
+ mark(404); // will end up with a 404 response sent by HttpChannel.handle
+ }
+ activeRequests.dec();
+ final long elapsedTime = System.currentTimeMillis() - start;
+ requests.update(elapsedTime, TimeUnit.MILLISECONDS);
+ requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
+ }
+
+ private void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(responseMeteredLevel)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ responses.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(prefix, String.format("%d-responses", sc))));
+ }
+
+ private Timer requestTimer(String method) {
+ final HttpMethod m = HttpMethod.fromString(method);
+ if (m == null) {
+ return otherRequests;
+ } else {
+ switch (m) {
+ case GET:
+ return getRequests;
+ case POST:
+ return postRequests;
+ case PUT:
+ return putRequests;
+ case HEAD:
+ return headRequests;
+ case DELETE:
+ return deleteRequests;
+ case OPTIONS:
+ return optionsRequests;
+ case TRACE:
+ return traceRequests;
+ case CONNECT:
+ return connectRequests;
+ case MOVE:
+ return moveRequests;
+ default:
+ return otherRequests;
+ }
+ }
+ }
+
+ private class AsyncAttachingListener implements AsyncListener {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ event.getAsyncContext().addListener(new InstrumentedAsyncListener());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {}
+ };
+
+ private class InstrumentedAsyncListener implements AsyncListener {
+ private final long startTime;
+
+ InstrumentedAsyncListener() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {
+ asyncTimeouts.mark();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {
+ final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
+ final HttpServletRequest request = (HttpServletRequest) state.getRequest();
+ final HttpServletResponse response = (HttpServletResponse) state.getResponse();
+ updateResponses(request, response, startTime, true);
+ if (!state.getHttpChannelState().isSuspended()) {
+ activeSuspended.dec();
+ }
+ }
+ }
+}
diff --git a/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedQueuedThreadPool.java b/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedQueuedThreadPool.java
new file mode 100644
index 0000000..92951cb
--- /dev/null
+++ b/metrics-jetty10/src/main/java/io/dropwizard/metrics/jetty10/InstrumentedQueuedThreadPool.java
@@ -0,0 +1,177 @@
+package io.dropwizard.metrics.jetty10;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
+ private static final String NAME_UTILIZATION = "utilization";
+ private static final String NAME_UTILIZATION_MAX = "utilization-max";
+ private static final String NAME_SIZE = "size";
+ private static final String NAME_JOBS = "jobs";
+ private static final String NAME_JOBS_QUEUE_UTILIZATION = "jobs-queue-utilization";
+
+ private final MetricRegistry metricRegistry;
+ private String prefix;
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry) {
+ this(registry, 200);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads) {
+ this(registry, maxThreads, 8);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads) {
+ this(registry, maxThreads, minThreads, 60000);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("queue") BlockingQueue<Runnable> queue) {
+ this(registry, maxThreads, minThreads, 60000, queue);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout) {
+ this(registry, maxThreads, minThreads, idleTimeout, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue<Runnable> queue) {
+ this(registry, maxThreads, minThreads, idleTimeout, queue, (ThreadGroup) 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) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, null, null, prefix);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadFactory") ThreadFactory threadFactory) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, null, threadFactory);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, threadGroup);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup) {
+ this(registry, maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup,
+ @Name("threadFactory") ThreadFactory threadFactory) {
+ this(registry, maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, threadFactory, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup,
+ @Name("threadFactory") ThreadFactory threadFactory,
+ @Name("prefix") String prefix) {
+ super(maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, threadFactory);
+ 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();
+
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.register(name(prefix, NAME_UTILIZATION), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(getThreads() - getIdleThreads(), getThreads());
+ }
+ });
+ metricRegistry.register(name(prefix, NAME_UTILIZATION_MAX), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(getThreads() - getIdleThreads(), getMaxThreads());
+ }
+ });
+ // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
+ // ArrayBlockingQueue for its queue, and is therefore a constant-time operation.
+ metricRegistry.registerGauge(name(prefix, NAME_SIZE), this::getThreads);
+ metricRegistry.registerGauge(name(prefix, NAME_JOBS), () -> getQueue().size());
+ metricRegistry.register(name(prefix, NAME_JOBS_QUEUE_UTILIZATION), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ BlockingQueue<Runnable> queue = getQueue();
+ return Ratio.of(queue.size(), queue.size() + queue.remainingCapacity());
+ }
+ });
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION));
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION_MAX));
+ metricRegistry.remove(name(prefix, NAME_SIZE));
+ metricRegistry.remove(name(prefix, NAME_JOBS));
+ metricRegistry.remove(name(prefix, NAME_JOBS_QUEUE_UTILIZATION));
+
+ super.doStop();
+ }
+
+ private String getMetricPrefix() {
+ return this.prefix == null ? name(QueuedThreadPool.class, getName()) : name(this.prefix, getName());
+ }
+}
diff --git a/metrics-jetty9-legacy/src/test/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactoryTest.java b/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedConnectionFactoryTest.java
similarity index 72%
rename from metrics-jetty9-legacy/src/test/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactoryTest.java
rename to metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedConnectionFactoryTest.java
index 5d825dd..b5d1b0c 100644
--- a/metrics-jetty9-legacy/src/test/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactoryTest.java
+++ b/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedConnectionFactoryTest.java
@@ -1,5 +1,6 @@
-package com.codahale.metrics.jetty9;
+package io.dropwizard.metrics.jetty10;
+import com.codahale.metrics.Counter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import org.eclipse.jetty.client.HttpClient;
@@ -19,7 +20,6 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
-import static com.codahale.metrics.MetricRegistry.name;
import static org.assertj.core.api.Assertions.assertThat;
public class InstrumentedConnectionFactoryTest {
@@ -27,7 +27,8 @@ public class InstrumentedConnectionFactoryTest {
private final Server server = new Server();
private final ServerConnector connector =
new ServerConnector(server, new InstrumentedConnectionFactory(new HttpConnectionFactory(),
- registry.timer("http.connections")));
+ registry.timer("http.connections"),
+ registry.counter("http.active-connections")));
private final HttpClient client = new HttpClient();
@Before
@@ -66,8 +67,27 @@ public class InstrumentedConnectionFactoryTest {
Thread.sleep(100); // make sure the connection is closed
- final Timer timer = registry.timer(name("http.connections"));
+ final Timer timer = registry.timer(MetricRegistry.name("http.connections"));
assertThat(timer.getCount())
.isEqualTo(1);
}
+
+ @Test
+ public void instrumentsActiveConnections() throws Exception {
+ final Counter counter = registry.counter("http.active-connections");
+
+ final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertThat(counter.getCount())
+ .isEqualTo(1);
+
+ client.stop(); // close the connection
+
+ Thread.sleep(100); // make sure the connection is closed
+
+ assertThat(counter.getCount())
+ .isEqualTo(0);
+ }
}
diff --git a/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedHandlerTest.java b/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedHandlerTest.java
new file mode 100644
index 0000000..248bf6e
--- /dev/null
+++ b/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedHandlerTest.java
@@ -0,0 +1,244 @@
+package io.dropwizard.metrics.jetty10;
+
+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.AbstractHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+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.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+public class InstrumentedHandlerTest {
+ private final HttpClient client = new HttpClient();
+ private final MetricRegistry registry = new MetricRegistry();
+ private final Server server = new Server();
+ private final ServerConnector connector = new ServerConnector(server);
+ private final InstrumentedHandler handler = new InstrumentedHandler(registry, null, ALL);
+
+ @Before
+ public void setUp() throws Exception {
+ handler.setName("handler");
+ handler.setHandler(new TestHandler());
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void hasAName() throws Exception {
+ assertThat(handler.getName())
+ .isEqualTo("handler");
+ }
+
+ @Test
+ public void createsAndRemovesMetricsForTheHandler() throws Exception {
+ final ContentResponse response = client.GET(uri("/hello"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(404);
+
+ assertThat(registry.getNames())
+ .containsOnly(
+ 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.404-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")
+ );
+
+ server.stop();
+
+ assertThat(registry.getNames())
+ .isEmpty();
+ }
+
+ @Test
+ public void responseTimesAreRecordedForBlockingResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/blocking"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertResponseTimesValid();
+ }
+
+ @Test
+ public void doStopDoesNotThrowNPE() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, null, ALL);
+ handler.setHandler(new TestHandler());
+
+ assertThatCode(handler::doStop).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void gaugesAreRegisteredWithResponseMeteredLevelCoarse() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, "coarse", COARSE);
+ handler.setHandler(new TestHandler());
+ handler.setName("handler");
+ handler.doStart();
+ assertThat(registry.getGauges()).containsKey("coarse.handler.percent-4xx-1m");
+ }
+
+ @Test
+ public void gaugesAreNotRegisteredWithResponseMeteredLevelDetailed() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, "detailed", DETAILED);
+ handler.setHandler(new TestHandler());
+ handler.setName("handler");
+ handler.doStart();
+ assertThat(registry.getGauges()).doesNotContainKey("coarse.handler.percent-4xx-1m");
+ }
+
+ @Test
+ @Ignore("flaky on virtual machines")
+ 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() + ".200-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.
+ * <p>
+ * Supports
+ * <p>
+ * /blocking - uses the standard servlet api
+ * /async - uses the 3.1 async api to complete the request
+ * <p>
+ * 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 {
+ switch (path) {
+ case "/blocking":
+ request.setHandled(true);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ httpServletResponse.getWriter().write("some content from the blocking request\n");
+ break;
+ case "/async":
+ request.setHandled(true);
+ final AsyncContext context = request.startAsync();
+ Thread t = new Thread(() -> {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ 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(StandardCharsets.UTF_8));
+ context.complete();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ context.complete();
+ }
+ }
+ );
+ } catch (IOException e) {
+ context.complete();
+ }
+ });
+ t.start();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedHttpChannelListenerTest.java b/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedHttpChannelListenerTest.java
new file mode 100644
index 0000000..800c9ff
--- /dev/null
+++ b/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedHttpChannelListenerTest.java
@@ -0,0 +1,212 @@
+package io.dropwizard.metrics.jetty10;
+
+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.AbstractHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedHttpChannelListenerTest {
+ private final HttpClient client = new HttpClient();
+ private final Server server = new Server();
+ private final ServerConnector connector = new ServerConnector(server);
+ private final TestHandler handler = new TestHandler();
+ private MetricRegistry registry;
+
+ @Before
+ public void setUp() throws Exception {
+ registry = new MetricRegistry();
+ connector.addBean(new InstrumentedHttpChannelListener(registry, MetricRegistry.name(TestHandler.class, "handler"), ALL));
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void createsMetricsForTheHandler() throws Exception {
+ final ContentResponse response = client.GET(uri("/hello"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(404);
+
+ assertThat(registry.getNames())
+ .containsOnly(
+ metricName("1xx-responses"),
+ metricName("2xx-responses"),
+ metricName("3xx-responses"),
+ metricName("404-responses"),
+ metricName("4xx-responses"),
+ metricName("5xx-responses"),
+ metricName("percent-4xx-1m"),
+ metricName("percent-4xx-5m"),
+ metricName("percent-4xx-15m"),
+ metricName("percent-5xx-1m"),
+ metricName("percent-5xx-5m"),
+ metricName("percent-5xx-15m"),
+ metricName("requests"),
+ metricName("active-suspended"),
+ metricName("async-dispatches"),
+ metricName("async-timeouts"),
+ metricName("get-requests"),
+ metricName("put-requests"),
+ metricName("active-dispatches"),
+ metricName("trace-requests"),
+ metricName("other-requests"),
+ metricName("connect-requests"),
+ metricName("dispatches"),
+ metricName("head-requests"),
+ metricName("post-requests"),
+ metricName("options-requests"),
+ metricName("active-requests"),
+ metricName("delete-requests"),
+ metricName("move-requests")
+ );
+ }
+
+
+ @Test
+ public void responseTimesAreRecordedForBlockingResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/blocking"));
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.getMediaType()).isEqualTo("text/plain");
+ assertThat(response.getContentAsString()).isEqualTo("some content from the blocking request");
+
+ assertResponseTimesValid();
+ }
+
+ @Test
+ public void responseTimesAreRecordedForAsyncResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/async"));
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.getMediaType()).isEqualTo("text/plain");
+ assertThat(response.getContentAsString()).isEqualTo("some content from the async");
+
+ assertResponseTimesValid();
+ }
+
+ private void assertResponseTimesValid() {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ assertThat(registry.getMeters().get(metricName("2xx-responses"))
+ .getCount()).isPositive();
+ assertThat(registry.getMeters().get(metricName("200-responses"))
+ .getCount()).isPositive();
+
+ assertThat(registry.getTimers().get(metricName("get-requests"))
+ .getSnapshot().getMedian()).isPositive();
+
+ assertThat(registry.getTimers().get(metricName("requests"))
+ .getSnapshot().getMedian()).isPositive();
+ }
+
+ private String uri(String path) {
+ return "http://localhost:" + connector.getLocalPort() + path;
+ }
+
+ private String metricName(String metricName) {
+ return MetricRegistry.name(TestHandler.class.getName(), "handler", metricName);
+ }
+
+ /**
+ * test handler.
+ * <p>
+ * Supports
+ * <p>
+ * /blocking - uses the standard servlet api
+ * /async - uses the 3.1 async api to complete the request
+ * <p>
+ * 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 {
+ switch (path) {
+ case "/blocking":
+ request.setHandled(true);
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ httpServletResponse.getWriter().write("some content from the blocking request");
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ httpServletResponse.setStatus(500);
+ Thread.currentThread().interrupt();
+ }
+ break;
+ case "/async":
+ request.setHandled(true);
+ final AsyncContext context = request.startAsync();
+ Thread t = new Thread(() -> {
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ httpServletResponse.setStatus(500);
+ Thread.currentThread().interrupt();
+ }
+ final ServletOutputStream servletOutputStream;
+ try {
+ servletOutputStream = httpServletResponse.getOutputStream();
+ servletOutputStream.setWriteListener(
+ new WriteListener() {
+ @Override
+ public void onWritePossible() throws IOException {
+ servletOutputStream.write("some content from the async"
+ .getBytes(StandardCharsets.UTF_8));
+ context.complete();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ context.complete();
+ }
+ }
+ );
+ } catch (IOException e) {
+ context.complete();
+ }
+ });
+ t.start();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedQueuedThreadPoolTest.java b/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedQueuedThreadPoolTest.java
new file mode 100644
index 0000000..4902e19
--- /dev/null
+++ b/metrics-jetty10/src/test/java/io/dropwizard/metrics/jetty10/InstrumentedQueuedThreadPoolTest.java
@@ -0,0 +1,49 @@
+package io.dropwizard.metrics.jetty10;
+
+import com.codahale.metrics.MetricRegistry;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedQueuedThreadPoolTest {
+ private static final String PREFIX = "prefix";
+
+ private MetricRegistry metricRegistry;
+ private InstrumentedQueuedThreadPool iqtp;
+
+ @Before
+ public void setUp() {
+ metricRegistry = new MetricRegistry();
+ iqtp = new InstrumentedQueuedThreadPool(metricRegistry);
+ }
+
+ @Test
+ public void customMetricsPrefix() throws Exception {
+ iqtp.setPrefix(PREFIX);
+ iqtp.start();
+
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("Custom metrics prefix doesn't match")
+ .allSatisfy(name -> assertThat(name).startsWith(PREFIX));
+
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
+ }
+
+ @Test
+ public void metricsPrefixBackwardCompatible() throws Exception {
+ iqtp.start();
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .allSatisfy(name -> assertThat(name).startsWith(QueuedThreadPool.class.getName()));
+
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
+ }
+}
diff --git a/metrics-jetty11/pom.xml b/metrics-jetty11/pom.xml
new file mode 100644
index 0000000..eb0ae81
--- /dev/null
+++ b/metrics-jetty11/pom.xml
@@ -0,0 +1,123 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jetty11</artifactId>
+ <name>Metrics Integration for Jetty 11.x and higher</name>
+ <packaging>bundle</packaging>
+ <description>
+ A set of extensions for Jetty 11.x and higher which provide instrumentation of thread pools, connector
+ metrics, and application latency and utilization.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.jetty11</javaModuleName>
+
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+
+ <slf4j.version>2.0.11</slf4j.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-bom</artifactId>
+ <version>${jetty11.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-jakarta-servlet-api</artifactId>
+ <version>5.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedConnectionFactory.java b/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedConnectionFactory.java
new file mode 100644
index 0000000..bce951e
--- /dev/null
+++ b/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedConnectionFactory.java
@@ -0,0 +1,63 @@
+package io.dropwizard.metrics.jetty11;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+
+import java.util.List;
+
+public class InstrumentedConnectionFactory extends ContainerLifeCycle implements ConnectionFactory {
+ private final ConnectionFactory connectionFactory;
+ private final Timer timer;
+ private final Counter counter;
+
+ public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer) {
+ this(connectionFactory, timer, null);
+ }
+
+ public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer, Counter counter) {
+ this.connectionFactory = connectionFactory;
+ this.timer = timer;
+ this.counter = counter;
+ addBean(connectionFactory);
+ }
+
+ @Override
+ public String getProtocol() {
+ return connectionFactory.getProtocol();
+ }
+
+ @Override
+ public List<String> getProtocols() {
+ return connectionFactory.getProtocols();
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint) {
+ final Connection connection = connectionFactory.newConnection(connector, endPoint);
+ connection.addEventListener(new Connection.Listener() {
+ private Timer.Context context;
+
+ @Override
+ public void onOpened(Connection connection) {
+ this.context = timer.time();
+ if (counter != null) {
+ counter.inc();
+ }
+ }
+
+ @Override
+ public void onClosed(Connection connection) {
+ context.stop();
+ if (counter != null) {
+ counter.dec();
+ }
+ }
+ });
+ return connection;
+ }
+}
diff --git a/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedHandler.java b/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedHandler.java
new file mode 100644
index 0000000..7914166
--- /dev/null
+++ b/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedHandler.java
@@ -0,0 +1,443 @@
+package io.dropwizard.metrics.jetty11;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import jakarta.servlet.AsyncEvent;
+import jakarta.servlet.AsyncListener;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.AsyncContextState;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+/**
+ * A Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
+ * instance.
+ */
+public class InstrumentedHandler extends HandlerWrapper {
+ private static final String NAME_REQUESTS = "requests";
+ private static final String NAME_DISPATCHES = "dispatches";
+ private static final String NAME_ACTIVE_REQUESTS = "active-requests";
+ private static final String NAME_ACTIVE_DISPATCHES = "active-dispatches";
+ private static final String NAME_ACTIVE_SUSPENDED = "active-suspended";
+ private static final String NAME_ASYNC_DISPATCHES = "async-dispatches";
+ private static final String NAME_ASYNC_TIMEOUTS = "async-timeouts";
+ private static final String NAME_1XX_RESPONSES = "1xx-responses";
+ private static final String NAME_2XX_RESPONSES = "2xx-responses";
+ private static final String NAME_3XX_RESPONSES = "3xx-responses";
+ private static final String NAME_4XX_RESPONSES = "4xx-responses";
+ private static final String NAME_5XX_RESPONSES = "5xx-responses";
+ private static final String NAME_GET_REQUESTS = "get-requests";
+ private static final String NAME_POST_REQUESTS = "post-requests";
+ private static final String NAME_HEAD_REQUESTS = "head-requests";
+ private static final String NAME_PUT_REQUESTS = "put-requests";
+ private static final String NAME_DELETE_REQUESTS = "delete-requests";
+ private static final String NAME_OPTIONS_REQUESTS = "options-requests";
+ private static final String NAME_TRACE_REQUESTS = "trace-requests";
+ private static final String NAME_CONNECT_REQUESTS = "connect-requests";
+ private static final String NAME_MOVE_REQUESTS = "move-requests";
+ private static final String NAME_OTHER_REQUESTS = "other-requests";
+ private static final String NAME_PERCENT_4XX_1M = "percent-4xx-1m";
+ private static final String NAME_PERCENT_4XX_5M = "percent-4xx-5m";
+ private static final String NAME_PERCENT_4XX_15M = "percent-4xx-15m";
+ private static final String NAME_PERCENT_5XX_1M = "percent-5xx-1m";
+ private static final String NAME_PERCENT_5XX_5M = "percent-5xx-5m";
+ private static final String NAME_PERCENT_5XX_15M = "percent-5xx-15m";
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+
+ private final MetricRegistry metricRegistry;
+
+ private String name;
+ private final String prefix;
+
+ // the requests handled by this handler, excluding active
+ private Timer requests;
+
+ // the number of dispatches seen by this handler, excluding active
+ private Timer dispatches;
+
+ // the number of active requests
+ private Counter activeRequests;
+
+ // the number of active dispatches
+ private Counter activeDispatches;
+
+ // the number of requests currently suspended.
+ private Counter activeSuspended;
+
+ // the number of requests that have been asynchronously dispatched
+ private Meter asyncDispatches;
+
+ // the number of requests that expired while suspended
+ private Meter asyncTimeouts;
+
+ private final ResponseMeteredLevel responseMeteredLevel;
+ private List<Meter> responses;
+ private Map<Integer, Meter> responseCodeMeters;
+
+ private Timer getRequests;
+ private Timer postRequests;
+ private Timer headRequests;
+ private Timer putRequests;
+ private Timer deleteRequests;
+ private Timer optionsRequests;
+ private Timer traceRequests;
+ private Timer connectRequests;
+ private Timer moveRequests;
+ private Timer otherRequests;
+
+ private AsyncListener listener;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ public InstrumentedHandler(MetricRegistry registry) {
+ this(registry, null);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ */
+ public InstrumentedHandler(MetricRegistry registry, String prefix) {
+ this(registry, prefix, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ * @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
+ */
+ public InstrumentedHandler(MetricRegistry registry, String prefix, ResponseMeteredLevel responseMeteredLevel) {
+ this.responseMeteredLevel = responseMeteredLevel;
+ this.metricRegistry = registry;
+ this.prefix = prefix;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ final String prefix = getMetricPrefix();
+
+ this.requests = metricRegistry.timer(name(prefix, NAME_REQUESTS));
+ this.dispatches = metricRegistry.timer(name(prefix, NAME_DISPATCHES));
+
+ this.activeRequests = metricRegistry.counter(name(prefix, NAME_ACTIVE_REQUESTS));
+ this.activeDispatches = metricRegistry.counter(name(prefix, NAME_ACTIVE_DISPATCHES));
+ this.activeSuspended = metricRegistry.counter(name(prefix, NAME_ACTIVE_SUSPENDED));
+
+ this.asyncDispatches = metricRegistry.meter(name(prefix, NAME_ASYNC_DISPATCHES));
+ this.asyncTimeouts = metricRegistry.meter(name(prefix, NAME_ASYNC_TIMEOUTS));
+
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(responseMeteredLevel) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+
+ this.getRequests = metricRegistry.timer(name(prefix, NAME_GET_REQUESTS));
+ this.postRequests = metricRegistry.timer(name(prefix, NAME_POST_REQUESTS));
+ this.headRequests = metricRegistry.timer(name(prefix, NAME_HEAD_REQUESTS));
+ this.putRequests = metricRegistry.timer(name(prefix, NAME_PUT_REQUESTS));
+ this.deleteRequests = metricRegistry.timer(name(prefix, NAME_DELETE_REQUESTS));
+ this.optionsRequests = metricRegistry.timer(name(prefix, NAME_OPTIONS_REQUESTS));
+ this.traceRequests = metricRegistry.timer(name(prefix, NAME_TRACE_REQUESTS));
+ this.connectRequests = metricRegistry.timer(name(prefix, NAME_CONNECT_REQUESTS));
+ this.moveRequests = metricRegistry.timer(name(prefix, NAME_MOVE_REQUESTS));
+ this.otherRequests = metricRegistry.timer(name(prefix, NAME_OTHER_REQUESTS));
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ this.responses = Collections.unmodifiableList(Arrays.asList(
+ metricRegistry.meter(name(prefix, NAME_1XX_RESPONSES)), // 1xx
+ metricRegistry.meter(name(prefix, NAME_2XX_RESPONSES)), // 2xx
+ metricRegistry.meter(name(prefix, NAME_3XX_RESPONSES)), // 3xx
+ metricRegistry.meter(name(prefix, NAME_4XX_RESPONSES)), // 4xx
+ metricRegistry.meter(name(prefix, NAME_5XX_RESPONSES)) // 5xx
+ ));
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_15M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_15M), new RatioGauge() {
+ @Override
+ public Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+ } else {
+ this.responses = Collections.emptyList();
+ }
+
+
+ this.listener = new AsyncAttachingListener();
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_SUSPENDED));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_TIMEOUTS));
+ metricRegistry.remove(name(prefix, NAME_1XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_2XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_3XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_4XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_5XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_GET_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_POST_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_HEAD_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PUT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DELETE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OPTIONS_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_TRACE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_CONNECT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_MOVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OTHER_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_15M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_15M));
+
+ if (responseCodeMeters != null) {
+ responseCodeMeters.keySet().stream()
+ .map(sc -> name(getMetricPrefix(), String.format("%d-responses", sc)))
+ .forEach(metricRegistry::remove);
+ }
+ super.doStop();
+ }
+
+ @Override
+ public void handle(String path,
+ Request request,
+ HttpServletRequest httpRequest,
+ HttpServletResponse httpResponse) throws IOException, ServletException {
+
+ activeDispatches.inc();
+
+ final long start;
+ final HttpChannelState state = request.getHttpChannelState();
+ if (state.isInitial()) {
+ // new request
+ activeRequests.inc();
+ start = request.getTimeStamp();
+ state.addListener(listener);
+ } else {
+ // resumed request
+ start = System.currentTimeMillis();
+ activeSuspended.dec();
+ if (state.getState() == HttpChannelState.State.HANDLING) {
+ asyncDispatches.mark();
+ }
+ }
+
+ try {
+ super.handle(path, request, httpRequest, httpResponse);
+ } finally {
+ final long now = System.currentTimeMillis();
+ final long dispatched = now - start;
+
+ activeDispatches.dec();
+ dispatches.update(dispatched, TimeUnit.MILLISECONDS);
+
+ if (state.isSuspended()) {
+ activeSuspended.inc();
+ } else if (state.isInitial()) {
+ updateResponses(httpRequest, httpResponse, start, request.isHandled());
+ }
+ // else onCompletion will handle it.
+ }
+ }
+
+ private Timer requestTimer(String method) {
+ final HttpMethod m = HttpMethod.fromString(method);
+ if (m == null) {
+ return otherRequests;
+ } else {
+ switch (m) {
+ case GET:
+ return getRequests;
+ case POST:
+ return postRequests;
+ case PUT:
+ return putRequests;
+ case HEAD:
+ return headRequests;
+ case DELETE:
+ return deleteRequests;
+ case OPTIONS:
+ return optionsRequests;
+ case TRACE:
+ return traceRequests;
+ case CONNECT:
+ return connectRequests;
+ case MOVE:
+ return moveRequests;
+ default:
+ return otherRequests;
+ }
+ }
+ }
+
+ private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start, boolean isHandled) {
+ if (isHandled) {
+ mark(response.getStatus());
+ } else {
+ mark(404);; // will end up with a 404 response sent by HttpChannel.handle
+ }
+ activeRequests.dec();
+ final long elapsedTime = System.currentTimeMillis() - start;
+ requests.update(elapsedTime, TimeUnit.MILLISECONDS);
+ requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
+ }
+
+ private void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(responseMeteredLevel)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ responses.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(getMetricPrefix(), String.format("%d-responses", sc))));
+ }
+
+ private String getMetricPrefix() {
+ return this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name);
+ }
+
+ private class AsyncAttachingListener implements AsyncListener {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ event.getAsyncContext().addListener(new InstrumentedAsyncListener());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {}
+ };
+
+ private class InstrumentedAsyncListener implements AsyncListener {
+ private final long startTime;
+
+ InstrumentedAsyncListener() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {
+ asyncTimeouts.mark();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {
+ final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
+ final HttpServletRequest request = (HttpServletRequest) state.getRequest();
+ final HttpServletResponse response = (HttpServletResponse) state.getResponse();
+ updateResponses(request, response, startTime, true);
+ if (!state.getHttpChannelState().isSuspended()) {
+ activeSuspended.dec();
+ }
+ }
+ }
+}
diff --git a/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedHttpChannelListener.java b/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedHttpChannelListener.java
new file mode 100644
index 0000000..ce36ebb
--- /dev/null
+++ b/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedHttpChannelListener.java
@@ -0,0 +1,424 @@
+package io.dropwizard.metrics.jetty11;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import jakarta.servlet.AsyncEvent;
+import jakarta.servlet.AsyncListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.AsyncContextState;
+import org.eclipse.jetty.server.HttpChannel.Listener;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+/**
+ * A Jetty {@link org.eclipse.jetty.server.HttpChannel.Listener} implementation which records various metrics about
+ * underlying channel instance. Unlike {@link InstrumentedHandler} that uses internal API, this class should be
+ * future proof. To install it, just add instance of this class to {@link org.eclipse.jetty.server.Connector} as bean.
+ *
+ * @since TBD
+ */
+public class InstrumentedHttpChannelListener
+ implements Listener {
+ private static final String START_ATTR = InstrumentedHttpChannelListener.class.getName() + ".start";
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+
+ private final MetricRegistry metricRegistry;
+
+ // the requests handled by this handler, excluding active
+ private final Timer requests;
+
+ // the number of dispatches seen by this handler, excluding active
+ private final Timer dispatches;
+
+ // the number of active requests
+ private final Counter activeRequests;
+
+ // the number of active dispatches
+ private final Counter activeDispatches;
+
+ // the number of requests currently suspended.
+ private final Counter activeSuspended;
+
+ // the number of requests that have been asynchronously dispatched
+ private final Meter asyncDispatches;
+
+ // the number of requests that expired while suspended
+ private final Meter asyncTimeouts;
+
+ private final ResponseMeteredLevel responseMeteredLevel;
+ private final List<Meter> responses;
+ private final Map<Integer, Meter> responseCodeMeters;
+ private final String prefix;
+ private final Timer getRequests;
+ private final Timer postRequests;
+ private final Timer headRequests;
+ private final Timer putRequests;
+ private final Timer deleteRequests;
+ private final Timer optionsRequests;
+ private final Timer traceRequests;
+ private final Timer connectRequests;
+ private final Timer moveRequests;
+ private final Timer otherRequests;
+
+ private final AsyncListener listener;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry) {
+ this(registry, null, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param pref the prefix to use for the metrics names
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry, String pref) {
+ this(registry, pref, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param pref the prefix to use for the metrics names
+ * @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry, String pref, ResponseMeteredLevel responseMeteredLevel) {
+ this.metricRegistry = registry;
+
+ this.prefix = (pref == null) ? getClass().getName() : pref;
+
+ this.requests = metricRegistry.timer(name(prefix, "requests"));
+ this.dispatches = metricRegistry.timer(name(prefix, "dispatches"));
+
+ this.activeRequests = metricRegistry.counter(name(prefix, "active-requests"));
+ this.activeDispatches = metricRegistry.counter(name(prefix, "active-dispatches"));
+ this.activeSuspended = metricRegistry.counter(name(prefix, "active-suspended"));
+
+ this.asyncDispatches = metricRegistry.meter(name(prefix, "async-dispatches"));
+ this.asyncTimeouts = metricRegistry.meter(name(prefix, "async-timeouts"));
+
+ this.responseMeteredLevel = responseMeteredLevel;
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(responseMeteredLevel) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+ this.responses = COARSE_METER_LEVELS.contains(responseMeteredLevel) ?
+ Collections.unmodifiableList(Arrays.asList(
+ registry.meter(name(prefix, "1xx-responses")), // 1xx
+ registry.meter(name(prefix, "2xx-responses")), // 2xx
+ registry.meter(name(prefix, "3xx-responses")), // 3xx
+ registry.meter(name(prefix, "4xx-responses")), // 4xx
+ registry.meter(name(prefix, "5xx-responses")) // 5xx
+ )) : Collections.emptyList();
+
+ this.getRequests = metricRegistry.timer(name(prefix, "get-requests"));
+ this.postRequests = metricRegistry.timer(name(prefix, "post-requests"));
+ this.headRequests = metricRegistry.timer(name(prefix, "head-requests"));
+ this.putRequests = metricRegistry.timer(name(prefix, "put-requests"));
+ this.deleteRequests = metricRegistry.timer(name(prefix, "delete-requests"));
+ this.optionsRequests = metricRegistry.timer(name(prefix, "options-requests"));
+ this.traceRequests = metricRegistry.timer(name(prefix, "trace-requests"));
+ this.connectRequests = metricRegistry.timer(name(prefix, "connect-requests"));
+ this.moveRequests = metricRegistry.timer(name(prefix, "move-requests"));
+ this.otherRequests = metricRegistry.timer(name(prefix, "other-requests"));
+
+ metricRegistry.register(name(prefix, "percent-4xx-1m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-4xx-5m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-4xx-15m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-1m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-5m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-15m"), new RatioGauge() {
+ @Override
+ public Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ this.listener = new AsyncAttachingListener();
+ }
+
+ @Override
+ public void onRequestBegin(final Request request) {
+
+ }
+
+ @Override
+ public void onBeforeDispatch(final Request request) {
+ before(request);
+ }
+
+ @Override
+ public void onDispatchFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onAfterDispatch(final Request request) {
+ after(request);
+ }
+
+ @Override
+ public void onRequestContent(final Request request, final ByteBuffer content) {
+
+ }
+
+ @Override
+ public void onRequestContentEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestTrailers(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onResponseBegin(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseCommit(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseContent(final Request request, final ByteBuffer content) {
+
+ }
+
+ @Override
+ public void onResponseEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onComplete(final Request request) {
+
+ }
+
+ private void before(final Request request) {
+ activeDispatches.inc();
+
+ final long start;
+ final HttpChannelState state = request.getHttpChannelState();
+ if (state.isInitial()) {
+ // new request
+ activeRequests.inc();
+ start = request.getTimeStamp();
+ state.addListener(listener);
+ } else {
+ // resumed request
+ start = System.currentTimeMillis();
+ activeSuspended.dec();
+ if (state.isAsyncStarted()) {
+ asyncDispatches.mark();
+ }
+ }
+ request.setAttribute(START_ATTR, start);
+ }
+
+ private void after(final Request request) {
+ final long start = (long) request.getAttribute(START_ATTR);
+ final long now = System.currentTimeMillis();
+ final long dispatched = now - start;
+
+ activeDispatches.dec();
+ dispatches.update(dispatched, TimeUnit.MILLISECONDS);
+
+ final HttpChannelState state = request.getHttpChannelState();
+ if (state.isSuspended()) {
+ activeSuspended.inc();
+ } else if (state.isInitial()) {
+ updateResponses(request, request.getResponse(), start, request.isHandled());
+ }
+ // else onCompletion will handle it.
+ }
+
+ private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start, boolean isHandled) {
+ if (isHandled) {
+ mark(response.getStatus());
+ } else {
+ mark(404); // will end up with a 404 response sent by HttpChannel.handle
+ }
+ activeRequests.dec();
+ final long elapsedTime = System.currentTimeMillis() - start;
+ requests.update(elapsedTime, TimeUnit.MILLISECONDS);
+ requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
+ }
+
+ private void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(responseMeteredLevel)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ responses.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(prefix, String.format("%d-responses", sc))));
+ }
+
+ private Timer requestTimer(String method) {
+ final HttpMethod m = HttpMethod.fromString(method);
+ if (m == null) {
+ return otherRequests;
+ } else {
+ switch (m) {
+ case GET:
+ return getRequests;
+ case POST:
+ return postRequests;
+ case PUT:
+ return putRequests;
+ case HEAD:
+ return headRequests;
+ case DELETE:
+ return deleteRequests;
+ case OPTIONS:
+ return optionsRequests;
+ case TRACE:
+ return traceRequests;
+ case CONNECT:
+ return connectRequests;
+ case MOVE:
+ return moveRequests;
+ default:
+ return otherRequests;
+ }
+ }
+ }
+
+ private class AsyncAttachingListener implements AsyncListener {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ event.getAsyncContext().addListener(new InstrumentedAsyncListener());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {}
+ };
+
+ private class InstrumentedAsyncListener implements AsyncListener {
+ private final long startTime;
+
+ InstrumentedAsyncListener() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {
+ asyncTimeouts.mark();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {
+ final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
+ final HttpServletRequest request = (HttpServletRequest) state.getRequest();
+ final HttpServletResponse response = (HttpServletResponse) state.getResponse();
+ updateResponses(request, response, startTime, true);
+ if (!state.getHttpChannelState().isSuspended()) {
+ activeSuspended.dec();
+ }
+ }
+ }
+}
diff --git a/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedQueuedThreadPool.java b/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedQueuedThreadPool.java
new file mode 100644
index 0000000..ac49f08
--- /dev/null
+++ b/metrics-jetty11/src/main/java/io/dropwizard/metrics/jetty11/InstrumentedQueuedThreadPool.java
@@ -0,0 +1,177 @@
+package io.dropwizard.metrics.jetty11;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
+ private static final String NAME_UTILIZATION = "utilization";
+ private static final String NAME_UTILIZATION_MAX = "utilization-max";
+ private static final String NAME_SIZE = "size";
+ private static final String NAME_JOBS = "jobs";
+ private static final String NAME_JOBS_QUEUE_UTILIZATION = "jobs-queue-utilization";
+
+ private final MetricRegistry metricRegistry;
+ private String prefix;
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry) {
+ this(registry, 200);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads) {
+ this(registry, maxThreads, 8);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads) {
+ this(registry, maxThreads, minThreads, 60000);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("queue") BlockingQueue<Runnable> queue) {
+ this(registry, maxThreads, minThreads, 60000, queue);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout) {
+ this(registry, maxThreads, minThreads, idleTimeout, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue<Runnable> queue) {
+ this(registry, maxThreads, minThreads, idleTimeout, queue, (ThreadGroup) 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) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, null, null, prefix);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadFactory") ThreadFactory threadFactory) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, null, threadFactory);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, threadGroup);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup) {
+ this(registry, maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup,
+ @Name("threadFactory") ThreadFactory threadFactory) {
+ this(registry, maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, threadFactory, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup,
+ @Name("threadFactory") ThreadFactory threadFactory,
+ @Name("prefix") String prefix) {
+ super(maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, threadFactory);
+ 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();
+
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.register(name(prefix, NAME_UTILIZATION), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(getThreads() - getIdleThreads(), getThreads());
+ }
+ });
+ metricRegistry.register(name(prefix, NAME_UTILIZATION_MAX), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(getThreads() - getIdleThreads(), getMaxThreads());
+ }
+ });
+ metricRegistry.registerGauge(name(prefix, NAME_SIZE), this::getThreads);
+ // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
+ // ArrayBlockingQueue for its queue, and is therefore a constant-time operation.
+ metricRegistry.registerGauge(name(prefix, NAME_JOBS), () -> getQueue().size());
+ metricRegistry.register(name(prefix, NAME_JOBS_QUEUE_UTILIZATION), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ BlockingQueue<Runnable> queue = getQueue();
+ return Ratio.of(queue.size(), queue.size() + queue.remainingCapacity());
+ }
+ });
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION));
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION_MAX));
+ metricRegistry.remove(name(prefix, NAME_SIZE));
+ metricRegistry.remove(name(prefix, NAME_JOBS));
+ metricRegistry.remove(name(prefix, NAME_JOBS_QUEUE_UTILIZATION));
+
+ super.doStop();
+ }
+
+ private String getMetricPrefix() {
+ return this.prefix == null ? name(QueuedThreadPool.class, getName()) : name(this.prefix, getName());
+ }
+}
diff --git a/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedConnectionFactoryTest.java b/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedConnectionFactoryTest.java
new file mode 100644
index 0000000..c12a77b
--- /dev/null
+++ b/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedConnectionFactoryTest.java
@@ -0,0 +1,93 @@
+package io.dropwizard.metrics.jetty11;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedConnectionFactoryTest {
+ private final MetricRegistry registry = new MetricRegistry();
+ private final Server server = new Server();
+ private final ServerConnector connector =
+ new ServerConnector(server, new InstrumentedConnectionFactory(new HttpConnectionFactory(),
+ registry.timer("http.connections"),
+ registry.counter("http.active-connections")));
+ private final HttpClient client = new HttpClient();
+
+ @Before
+ public void setUp() throws Exception {
+ server.setHandler(new AbstractHandler() {
+ @Override
+ public void handle(String target,
+ Request baseRequest,
+ HttpServletRequest request,
+ HttpServletResponse response) throws IOException, ServletException {
+ try (PrintWriter writer = response.getWriter()) {
+ writer.println("OK");
+ }
+ }
+ });
+
+ server.addConnector(connector);
+ server.start();
+
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void instrumentsConnectionTimes() throws Exception {
+ final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ client.stop(); // close the connection
+
+ Thread.sleep(100); // make sure the connection is closed
+
+ final Timer timer = registry.timer(MetricRegistry.name("http.connections"));
+ assertThat(timer.getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void instrumentsActiveConnections() throws Exception {
+ final Counter counter = registry.counter("http.active-connections");
+
+ final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertThat(counter.getCount())
+ .isEqualTo(1);
+
+ client.stop(); // close the connection
+
+ Thread.sleep(100); // make sure the connection is closed
+
+ assertThat(counter.getCount())
+ .isEqualTo(0);
+ }
+}
diff --git a/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedHandlerTest.java b/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedHandlerTest.java
new file mode 100644
index 0000000..ca18793
--- /dev/null
+++ b/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedHandlerTest.java
@@ -0,0 +1,247 @@
+package io.dropwizard.metrics.jetty11;
+
+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.AbstractHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import jakarta.servlet.AsyncContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+public class InstrumentedHandlerTest {
+ private final HttpClient client = new HttpClient();
+ private final MetricRegistry registry = new MetricRegistry();
+ private final Server server = new Server();
+ private final ServerConnector connector = new ServerConnector(server);
+ private final InstrumentedHandler handler = new InstrumentedHandler(registry, null, ALL);
+
+ @Before
+ public void setUp() throws Exception {
+ handler.setName("handler");
+ handler.setHandler(new TestHandler());
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void hasAName() throws Exception {
+ assertThat(handler.getName())
+ .isEqualTo("handler");
+ }
+
+ @Test
+ public void createsAndRemovesMetricsForTheHandler() throws Exception {
+ final ContentResponse response = client.GET(uri("/hello"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(404);
+
+ assertThat(registry.getNames())
+ .containsOnly(
+ 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.404-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")
+ );
+
+ server.stop();
+
+ assertThat(registry.getNames())
+ .isEmpty();
+ }
+
+ @Test
+ public void responseTimesAreRecordedForBlockingResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/blocking"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertResponseTimesValid();
+ }
+
+ @Test
+ public void doStopDoesNotThrowNPE() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, null, ALL);
+ handler.setHandler(new TestHandler());
+
+ assertThatCode(handler::doStop).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void gaugesAreRegisteredWithResponseMeteredLevelCoarse() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, "coarse", COARSE);
+ handler.setHandler(new TestHandler());
+ handler.setName("handler");
+ handler.doStart();
+ assertThat(registry.getGauges()).containsKey("coarse.handler.percent-4xx-1m");
+ }
+
+ @Test
+ public void gaugesAreNotRegisteredWithResponseMeteredLevelDetailed() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, "detailed", DETAILED);
+ handler.setHandler(new TestHandler());
+ handler.setName("handler");
+ handler.doStart();
+ assertThat(registry.getGauges()).doesNotContainKey("coarse.handler.percent-4xx-1m");
+ }
+
+ @Test
+ @Ignore("flaky on virtual machines")
+ 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.getMeters().get(metricName() + ".200-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.
+ * <p>
+ * Supports
+ * <p>
+ * /blocking - uses the standard servlet api
+ * /async - uses the 3.1 async api to complete the request
+ * <p>
+ * 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 {
+ switch (path) {
+ case "/blocking":
+ request.setHandled(true);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ httpServletResponse.getWriter().write("some content from the blocking request\n");
+ break;
+ case "/async":
+ request.setHandled(true);
+ final AsyncContext context = request.startAsync();
+ Thread t = new Thread(() -> {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ 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(StandardCharsets.UTF_8));
+ context.complete();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ context.complete();
+ }
+ }
+ );
+ } catch (IOException e) {
+ context.complete();
+ }
+ });
+ t.start();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedHttpChannelListenerTest.java b/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedHttpChannelListenerTest.java
new file mode 100644
index 0000000..5badb80
--- /dev/null
+++ b/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedHttpChannelListenerTest.java
@@ -0,0 +1,212 @@
+package io.dropwizard.metrics.jetty11;
+
+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.AbstractHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import jakarta.servlet.AsyncContext;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedHttpChannelListenerTest {
+ private final HttpClient client = new HttpClient();
+ private final Server server = new Server();
+ private final ServerConnector connector = new ServerConnector(server);
+ private final TestHandler handler = new TestHandler();
+ private MetricRegistry registry;
+
+ @Before
+ public void setUp() throws Exception {
+ registry = new MetricRegistry();
+ connector.addBean(new InstrumentedHttpChannelListener(registry, MetricRegistry.name(TestHandler.class, "handler"), ALL));
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void createsMetricsForTheHandler() throws Exception {
+ final ContentResponse response = client.GET(uri("/hello"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(404);
+
+ assertThat(registry.getNames())
+ .containsOnly(
+ metricName("1xx-responses"),
+ metricName("2xx-responses"),
+ metricName("3xx-responses"),
+ metricName("404-responses"),
+ metricName("4xx-responses"),
+ metricName("5xx-responses"),
+ metricName("percent-4xx-1m"),
+ metricName("percent-4xx-5m"),
+ metricName("percent-4xx-15m"),
+ metricName("percent-5xx-1m"),
+ metricName("percent-5xx-5m"),
+ metricName("percent-5xx-15m"),
+ metricName("requests"),
+ metricName("active-suspended"),
+ metricName("async-dispatches"),
+ metricName("async-timeouts"),
+ metricName("get-requests"),
+ metricName("put-requests"),
+ metricName("active-dispatches"),
+ metricName("trace-requests"),
+ metricName("other-requests"),
+ metricName("connect-requests"),
+ metricName("dispatches"),
+ metricName("head-requests"),
+ metricName("post-requests"),
+ metricName("options-requests"),
+ metricName("active-requests"),
+ metricName("delete-requests"),
+ metricName("move-requests")
+ );
+ }
+
+
+ @Test
+ public void responseTimesAreRecordedForBlockingResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/blocking"));
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.getMediaType()).isEqualTo("text/plain");
+ assertThat(response.getContentAsString()).isEqualTo("some content from the blocking request");
+
+ assertResponseTimesValid();
+ }
+
+ @Test
+ public void responseTimesAreRecordedForAsyncResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/async"));
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.getMediaType()).isEqualTo("text/plain");
+ assertThat(response.getContentAsString()).isEqualTo("some content from the async");
+
+ assertResponseTimesValid();
+ }
+
+ private void assertResponseTimesValid() {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ assertThat(registry.getMeters().get(metricName("2xx-responses"))
+ .getCount()).isPositive();
+ assertThat(registry.getMeters().get(metricName("200-responses"))
+ .getCount()).isPositive();
+
+ assertThat(registry.getTimers().get(metricName("get-requests"))
+ .getSnapshot().getMedian()).isPositive();
+
+ assertThat(registry.getTimers().get(metricName("requests"))
+ .getSnapshot().getMedian()).isPositive();
+ }
+
+ private String uri(String path) {
+ return "http://localhost:" + connector.getLocalPort() + path;
+ }
+
+ private String metricName(String metricName) {
+ return MetricRegistry.name(TestHandler.class.getName(), "handler", metricName);
+ }
+
+ /**
+ * test handler.
+ * <p>
+ * Supports
+ * <p>
+ * /blocking - uses the standard servlet api
+ * /async - uses the 3.1 async api to complete the request
+ * <p>
+ * 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 {
+ switch (path) {
+ case "/blocking":
+ request.setHandled(true);
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ httpServletResponse.getWriter().write("some content from the blocking request");
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ httpServletResponse.setStatus(500);
+ Thread.currentThread().interrupt();
+ }
+ break;
+ case "/async":
+ request.setHandled(true);
+ final AsyncContext context = request.startAsync();
+ Thread t = new Thread(() -> {
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ httpServletResponse.setStatus(500);
+ Thread.currentThread().interrupt();
+ }
+ final ServletOutputStream servletOutputStream;
+ try {
+ servletOutputStream = httpServletResponse.getOutputStream();
+ servletOutputStream.setWriteListener(
+ new WriteListener() {
+ @Override
+ public void onWritePossible() throws IOException {
+ servletOutputStream.write("some content from the async"
+ .getBytes(StandardCharsets.UTF_8));
+ context.complete();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ context.complete();
+ }
+ }
+ );
+ } catch (IOException e) {
+ context.complete();
+ }
+ });
+ t.start();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedQueuedThreadPoolTest.java b/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedQueuedThreadPoolTest.java
new file mode 100644
index 0000000..e373239
--- /dev/null
+++ b/metrics-jetty11/src/test/java/io/dropwizard/metrics/jetty11/InstrumentedQueuedThreadPoolTest.java
@@ -0,0 +1,49 @@
+package io.dropwizard.metrics.jetty11;
+
+import com.codahale.metrics.MetricRegistry;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedQueuedThreadPoolTest {
+ private static final String PREFIX = "prefix";
+
+ private MetricRegistry metricRegistry;
+ private InstrumentedQueuedThreadPool iqtp;
+
+ @Before
+ public void setUp() {
+ metricRegistry = new MetricRegistry();
+ iqtp = new InstrumentedQueuedThreadPool(metricRegistry);
+ }
+
+ @Test
+ public void customMetricsPrefix() throws Exception {
+ iqtp.setPrefix(PREFIX);
+ iqtp.start();
+
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("Custom metrics prefix doesn't match")
+ .allSatisfy(name -> assertThat(name).startsWith(PREFIX));
+
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
+ }
+
+ @Test
+ public void metricsPrefixBackwardCompatible() throws Exception {
+ iqtp.start();
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .allSatisfy(name -> assertThat(name).startsWith(QueuedThreadPool.class.getName()));
+
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
+ }
+}
diff --git a/metrics-jetty12-ee10/pom.xml b/metrics-jetty12-ee10/pom.xml
new file mode 100644
index 0000000..372023a
--- /dev/null
+++ b/metrics-jetty12-ee10/pom.xml
@@ -0,0 +1,138 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jetty12-ee10</artifactId>
+ <name>Metrics Integration for Jetty 12.x and higher with Jakarta EE 10</name>
+ <packaging>bundle</packaging>
+ <description>
+ A set of extensions for Jetty 12.x and higher which provide instrumentation of thread pools, connector
+ metrics, and application latency and utilization. This module uses the Servlet API from Jakarta EE 10.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.jetty12.ee10</javaModuleName>
+
+ <maven.compiler.release>17</maven.compiler.release>
+
+ <slf4j.version>2.0.11</slf4j.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-bom</artifactId>
+ <version>${jetty12.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.ee10</groupId>
+ <artifactId>jetty-ee10-bom</artifactId>
+ <version>${jetty12.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <version>${servlet6.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-jetty12</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.ee10</groupId>
+ <artifactId>jetty-ee10-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java b/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java
new file mode 100644
index 0000000..8a65e82
--- /dev/null
+++ b/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java
@@ -0,0 +1,176 @@
+package io.dropwizard.metrics.jetty12.ee10;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import io.dropwizard.metrics.jetty12.AbstractInstrumentedHandler;
+import jakarta.servlet.AsyncEvent;
+import jakarta.servlet.AsyncListener;
+import org.eclipse.jetty.ee10.servlet.AsyncContextState;
+import org.eclipse.jetty.ee10.servlet.ServletApiRequest;
+import org.eclipse.jetty.ee10.servlet.ServletApiResponse;
+import org.eclipse.jetty.ee10.servlet.ServletChannelState;
+import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+
+/**
+ * A Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
+ * instance. This {@link Handler} requires a {@link org.eclipse.jetty.ee10.servlet.ServletContextHandler} to be present.
+ * For correct behaviour, the {@link org.eclipse.jetty.ee10.servlet.ServletContextHandler} should be before this handler
+ * in the handler chain. To achieve this, one can use
+ * {@link org.eclipse.jetty.ee10.servlet.ServletContextHandler#insertHandler(Singleton)}.
+ */
+public class InstrumentedEE10Handler extends AbstractInstrumentedHandler {
+ private AsyncListener listener;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ public InstrumentedEE10Handler(MetricRegistry registry) {
+ super(registry, null);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ */
+ public InstrumentedEE10Handler(MetricRegistry registry, String prefix) {
+ super(registry, prefix, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ * @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
+ */
+ public InstrumentedEE10Handler(MetricRegistry registry, String prefix, ResponseMeteredLevel responseMeteredLevel) {
+ super(registry, prefix, responseMeteredLevel);
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ this.listener = new AsyncAttachingListener();
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ super.doStop();
+ }
+
+ @Override
+ public boolean handle(Request request, Response response, Callback callback) throws Exception {
+ ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
+
+ // only handle servlet requests with the InstrumentedHandler
+ // because it depends on the ServletRequestState
+ if (servletContextRequest == null) {
+ return super.handle(request, response, callback);
+ }
+
+ activeDispatches.inc();
+
+ final long start;
+ final ServletChannelState state = servletContextRequest.getServletRequestState();
+ if (state.isInitial()) {
+ // new request
+ activeRequests.inc();
+ start = Request.getTimeStamp(request);
+ state.addListener(listener);
+ } else {
+ // resumed request
+ start = System.currentTimeMillis();
+ activeSuspended.dec();
+ if (state.getState() == ServletChannelState.State.HANDLING) {
+ asyncDispatches.mark();
+ }
+ }
+
+ boolean handled = false;
+
+ try {
+ handled = super.handle(request, response, callback);
+ } finally {
+ final long now = System.currentTimeMillis();
+ final long dispatched = now - start;
+
+ activeDispatches.dec();
+ dispatches.update(dispatched, TimeUnit.MILLISECONDS);
+
+ if (state.isSuspended()) {
+ activeSuspended.inc();
+ } else if (state.isInitial()) {
+ updateResponses(request, response, start, handled);
+ }
+ // else onCompletion will handle it.
+ }
+
+ return handled;
+ }
+
+ private class AsyncAttachingListener implements AsyncListener {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ event.getAsyncContext().addListener(new InstrumentedAsyncListener());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {}
+ }
+
+ private class InstrumentedAsyncListener implements AsyncListener {
+ private final long startTime;
+
+ InstrumentedAsyncListener() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {
+ asyncTimeouts.mark();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {
+ final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
+ final ServletApiRequest request = (ServletApiRequest) state.getRequest();
+ final ServletApiResponse response = (ServletApiResponse) state.getResponse();
+ updateResponses(request.getRequest(), response.getResponse(), startTime, true);
+
+ final ServletContextRequest servletContextRequest = Request.as(request.getRequest(), ServletContextRequest.class);
+ final ServletChannelState servletRequestState = servletContextRequest.getServletRequestState();
+ if (!servletRequestState.isSuspended()) {
+ activeSuspended.dec();
+ }
+ }
+ }
+}
diff --git a/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java
new file mode 100644
index 0000000..e2c56ac
--- /dev/null
+++ b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java
@@ -0,0 +1,272 @@
+package io.dropwizard.metrics.jetty12.ee10;
+
+import com.codahale.metrics.MetricRegistry;
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.ee10.servlet.DefaultServlet;
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
+import org.eclipse.jetty.ee10.servlet.ServletHandler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Callback;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import jakarta.servlet.AsyncContext;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+public class InstrumentedEE10HandlerTest {
+ private final HttpClient client = new HttpClient();
+ private final MetricRegistry registry = new MetricRegistry();
+ private final Server server = new Server();
+ private final ServerConnector connector = new ServerConnector(server);
+ private final InstrumentedEE10Handler handler = new InstrumentedEE10Handler(registry, null, ALL);
+
+ @Before
+ public void setUp() throws Exception {
+ handler.setName("handler");
+
+ TestHandler testHandler = new TestHandler();
+ // a servlet handler needs a servlet mapping, else the request will be short-circuited
+ // so use the DefaultServlet here
+ testHandler.addServletWithMapping(DefaultServlet.class, "/");
+
+ // builds the following handler chain:
+ // ServletContextHandler -> InstrumentedHandler -> TestHandler
+ // the ServletContextHandler is needed to utilize servlet related classes
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setHandler(testHandler);
+ servletContextHandler.insertHandler(handler);
+ server.setHandler(servletContextHandler);
+
+ server.addConnector(connector);
+ server.start();
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void hasAName() throws Exception {
+ assertThat(handler.getName())
+ .isEqualTo("handler");
+ }
+
+ @Test
+ public void createsAndRemovesMetricsForTheHandler() throws Exception {
+ final ContentResponse response = client.GET(uri("/hello"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(404);
+
+ assertThat(registry.getNames())
+ .containsOnly(
+ 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.404-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")
+ );
+
+ server.stop();
+
+ assertThat(registry.getNames())
+ .isEmpty();
+ }
+
+ @Test
+ @Ignore("flaky on virtual machines")
+ public void responseTimesAreRecordedForBlockingResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/blocking"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertResponseTimesValid();
+ }
+
+ @Test
+ public void doStopDoesNotThrowNPE() throws Exception {
+ InstrumentedEE10Handler handler = new InstrumentedEE10Handler(registry, null, ALL);
+ handler.setHandler(new TestHandler());
+
+ assertThatCode(handler::doStop).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void gaugesAreRegisteredWithResponseMeteredLevelCoarse() throws Exception {
+ InstrumentedEE10Handler handler = new InstrumentedEE10Handler(registry, "coarse", COARSE);
+ handler.setHandler(new TestHandler());
+ handler.setName("handler");
+ handler.doStart();
+ assertThat(registry.getGauges()).containsKey("coarse.handler.percent-4xx-1m");
+ }
+
+ @Test
+ public void gaugesAreNotRegisteredWithResponseMeteredLevelDetailed() throws Exception {
+ InstrumentedEE10Handler handler = new InstrumentedEE10Handler(registry, "detailed", DETAILED);
+ handler.setHandler(new TestHandler());
+ handler.setName("handler");
+ handler.doStart();
+ assertThat(registry.getGauges()).doesNotContainKey("coarse.handler.percent-4xx-1m");
+ }
+
+ @Test
+ @Ignore("flaky on virtual machines")
+ 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.getMeters().get(metricName() + ".200-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.
+ * <p>
+ * Supports
+ * <p>
+ * /blocking - uses the standard servlet api
+ * /async - uses the 3.1 async api to complete the request
+ * <p>
+ * all other requests will return 404
+ */
+ private static class TestHandler extends ServletHandler {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback) throws Exception {
+ ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
+ if (servletContextRequest == null) {
+ return false;
+ }
+
+ HttpServletRequest httpServletRequest = servletContextRequest.getServletApiRequest();
+ HttpServletResponse httpServletResponse = servletContextRequest.getHttpServletResponse();
+
+ String path = request.getHttpURI().getPath();
+ switch (path) {
+ case "/blocking":
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ httpServletResponse.getWriter().write("some content from the blocking request\n");
+ callback.succeeded();
+ return true;
+ case "/async":
+ servletContextRequest.getState().handling();
+ final AsyncContext context = httpServletRequest.startAsync();
+ Thread t = new Thread(() -> {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ 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(StandardCharsets.UTF_8));
+ context.complete();
+ servletContextRequest.getServletChannel().handle();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ context.complete();
+ servletContextRequest.getServletChannel().handle();
+ }
+ }
+ );
+ servletContextRequest.getHttpOutput().run();
+ } catch (IOException e) {
+ context.complete();
+ servletContextRequest.getServletChannel().handle();
+ }
+ });
+ t.start();
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/metrics-jetty12/pom.xml b/metrics-jetty12/pom.xml
new file mode 100644
index 0000000..396c48e
--- /dev/null
+++ b/metrics-jetty12/pom.xml
@@ -0,0 +1,117 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-jetty12</artifactId>
+ <name>Metrics Integration for Jetty 12.x and higher</name>
+ <packaging>bundle</packaging>
+ <description>
+ A set of extensions for Jetty 12.x and higher which provide instrumentation of thread pools, connector
+ metrics, and application latency and utilization.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.jetty12</javaModuleName>
+
+ <maven.compiler.release>17</maven.compiler.release>
+
+ <slf4j.version>2.0.11</slf4j.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-bom</artifactId>
+ <version>${jetty12.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java
new file mode 100644
index 0000000..d2a7899
--- /dev/null
+++ b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java
@@ -0,0 +1,340 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+/**
+ * An abstract base class of a Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
+ * instance.
+ */
+public abstract class AbstractInstrumentedHandler extends Handler.Wrapper {
+ protected static final String NAME_REQUESTS = "requests";
+ protected static final String NAME_DISPATCHES = "dispatches";
+ protected static final String NAME_ACTIVE_REQUESTS = "active-requests";
+ protected static final String NAME_ACTIVE_DISPATCHES = "active-dispatches";
+ protected static final String NAME_ACTIVE_SUSPENDED = "active-suspended";
+ protected static final String NAME_ASYNC_DISPATCHES = "async-dispatches";
+ protected static final String NAME_ASYNC_TIMEOUTS = "async-timeouts";
+ protected static final String NAME_1XX_RESPONSES = "1xx-responses";
+ protected static final String NAME_2XX_RESPONSES = "2xx-responses";
+ protected static final String NAME_3XX_RESPONSES = "3xx-responses";
+ protected static final String NAME_4XX_RESPONSES = "4xx-responses";
+ protected static final String NAME_5XX_RESPONSES = "5xx-responses";
+ protected static final String NAME_GET_REQUESTS = "get-requests";
+ protected static final String NAME_POST_REQUESTS = "post-requests";
+ protected static final String NAME_HEAD_REQUESTS = "head-requests";
+ protected static final String NAME_PUT_REQUESTS = "put-requests";
+ protected static final String NAME_DELETE_REQUESTS = "delete-requests";
+ protected static final String NAME_OPTIONS_REQUESTS = "options-requests";
+ protected static final String NAME_TRACE_REQUESTS = "trace-requests";
+ protected static final String NAME_CONNECT_REQUESTS = "connect-requests";
+ protected static final String NAME_MOVE_REQUESTS = "move-requests";
+ protected static final String NAME_OTHER_REQUESTS = "other-requests";
+ protected static final String NAME_PERCENT_4XX_1M = "percent-4xx-1m";
+ protected static final String NAME_PERCENT_4XX_5M = "percent-4xx-5m";
+ protected static final String NAME_PERCENT_4XX_15M = "percent-4xx-15m";
+ protected static final String NAME_PERCENT_5XX_1M = "percent-5xx-1m";
+ protected static final String NAME_PERCENT_5XX_5M = "percent-5xx-5m";
+ protected static final String NAME_PERCENT_5XX_15M = "percent-5xx-15m";
+ protected static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ protected static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+
+ protected final MetricRegistry metricRegistry;
+
+ private String name;
+ protected final String prefix;
+
+ // the requests handled by this handler, excluding active
+ protected Timer requests;
+
+ // the number of dispatches seen by this handler, excluding active
+ protected Timer dispatches;
+
+ // the number of active requests
+ protected Counter activeRequests;
+
+ // the number of active dispatches
+ protected Counter activeDispatches;
+
+ // the number of requests currently suspended.
+ protected Counter activeSuspended;
+
+ // the number of requests that have been asynchronously dispatched
+ protected Meter asyncDispatches;
+
+ // the number of requests that expired while suspended
+ protected Meter asyncTimeouts;
+
+ protected final ResponseMeteredLevel responseMeteredLevel;
+ protected List<Meter> responses;
+ protected Map<Integer, Meter> responseCodeMeters;
+
+ protected Timer getRequests;
+ protected Timer postRequests;
+ protected Timer headRequests;
+ protected Timer putRequests;
+ protected Timer deleteRequests;
+ protected Timer optionsRequests;
+ protected Timer traceRequests;
+ protected Timer connectRequests;
+ protected Timer moveRequests;
+ protected Timer otherRequests;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ protected AbstractInstrumentedHandler(MetricRegistry registry) {
+ this(registry, null);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ */
+ protected AbstractInstrumentedHandler(MetricRegistry registry, String prefix) {
+ this(registry, prefix, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ * @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
+ */
+ protected AbstractInstrumentedHandler(MetricRegistry registry, String prefix, ResponseMeteredLevel responseMeteredLevel) {
+ this.responseMeteredLevel = responseMeteredLevel;
+ this.metricRegistry = registry;
+ this.prefix = prefix;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ final String prefix = getMetricPrefix();
+
+ this.requests = metricRegistry.timer(name(prefix, NAME_REQUESTS));
+ this.dispatches = metricRegistry.timer(name(prefix, NAME_DISPATCHES));
+
+ this.activeRequests = metricRegistry.counter(name(prefix, NAME_ACTIVE_REQUESTS));
+ this.activeDispatches = metricRegistry.counter(name(prefix, NAME_ACTIVE_DISPATCHES));
+ this.activeSuspended = metricRegistry.counter(name(prefix, NAME_ACTIVE_SUSPENDED));
+
+ this.asyncDispatches = metricRegistry.meter(name(prefix, NAME_ASYNC_DISPATCHES));
+ this.asyncTimeouts = metricRegistry.meter(name(prefix, NAME_ASYNC_TIMEOUTS));
+
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(responseMeteredLevel) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+
+ this.getRequests = metricRegistry.timer(name(prefix, NAME_GET_REQUESTS));
+ this.postRequests = metricRegistry.timer(name(prefix, NAME_POST_REQUESTS));
+ this.headRequests = metricRegistry.timer(name(prefix, NAME_HEAD_REQUESTS));
+ this.putRequests = metricRegistry.timer(name(prefix, NAME_PUT_REQUESTS));
+ this.deleteRequests = metricRegistry.timer(name(prefix, NAME_DELETE_REQUESTS));
+ this.optionsRequests = metricRegistry.timer(name(prefix, NAME_OPTIONS_REQUESTS));
+ this.traceRequests = metricRegistry.timer(name(prefix, NAME_TRACE_REQUESTS));
+ this.connectRequests = metricRegistry.timer(name(prefix, NAME_CONNECT_REQUESTS));
+ this.moveRequests = metricRegistry.timer(name(prefix, NAME_MOVE_REQUESTS));
+ this.otherRequests = metricRegistry.timer(name(prefix, NAME_OTHER_REQUESTS));
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ this.responses = Collections.unmodifiableList(Arrays.asList(
+ metricRegistry.meter(name(prefix, NAME_1XX_RESPONSES)), // 1xx
+ metricRegistry.meter(name(prefix, NAME_2XX_RESPONSES)), // 2xx
+ metricRegistry.meter(name(prefix, NAME_3XX_RESPONSES)), // 3xx
+ metricRegistry.meter(name(prefix, NAME_4XX_RESPONSES)), // 4xx
+ metricRegistry.meter(name(prefix, NAME_5XX_RESPONSES)) // 5xx
+ ));
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_15M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_15M), new RatioGauge() {
+ @Override
+ public Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+ } else {
+ this.responses = Collections.emptyList();
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_SUSPENDED));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_TIMEOUTS));
+ metricRegistry.remove(name(prefix, NAME_1XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_2XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_3XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_4XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_5XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_GET_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_POST_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_HEAD_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PUT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DELETE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OPTIONS_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_TRACE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_CONNECT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_MOVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OTHER_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_15M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_15M));
+
+ if (responseCodeMeters != null) {
+ responseCodeMeters.keySet().stream()
+ .map(sc -> name(getMetricPrefix(), String.format("%d-responses", sc)))
+ .forEach(metricRegistry::remove);
+ }
+ super.doStop();
+ }
+
+ protected Timer requestTimer(String method) {
+ final HttpMethod m = HttpMethod.fromString(method);
+ if (m == null) {
+ return otherRequests;
+ } else {
+ switch (m) {
+ case GET:
+ return getRequests;
+ case POST:
+ return postRequests;
+ case PUT:
+ return putRequests;
+ case HEAD:
+ return headRequests;
+ case DELETE:
+ return deleteRequests;
+ case OPTIONS:
+ return optionsRequests;
+ case TRACE:
+ return traceRequests;
+ case CONNECT:
+ return connectRequests;
+ case MOVE:
+ return moveRequests;
+ default:
+ return otherRequests;
+ }
+ }
+ }
+
+ protected void updateResponses(Request request, Response response, long start, boolean isHandled) {
+ if (isHandled) {
+ mark(response.getStatus());
+ } else {
+ mark(404);; // will end up with a 404 response sent by HttpChannel.handle
+ }
+ activeRequests.dec();
+ final long elapsedTime = System.currentTimeMillis() - start;
+ requests.update(elapsedTime, TimeUnit.MILLISECONDS);
+ requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
+ }
+
+ protected void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(responseMeteredLevel)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ responses.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ protected Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(getMetricPrefix(), String.format("%d-responses", sc))));
+ }
+
+ protected String getMetricPrefix() {
+ return this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name);
+ }
+}
diff --git a/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactory.java b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactory.java
new file mode 100644
index 0000000..679d310
--- /dev/null
+++ b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactory.java
@@ -0,0 +1,63 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+
+import java.util.List;
+
+public class InstrumentedConnectionFactory extends ContainerLifeCycle implements ConnectionFactory {
+ private final ConnectionFactory connectionFactory;
+ private final Timer timer;
+ private final Counter counter;
+
+ public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer) {
+ this(connectionFactory, timer, null);
+ }
+
+ public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer, Counter counter) {
+ this.connectionFactory = connectionFactory;
+ this.timer = timer;
+ this.counter = counter;
+ addBean(connectionFactory);
+ }
+
+ @Override
+ public String getProtocol() {
+ return connectionFactory.getProtocol();
+ }
+
+ @Override
+ public List<String> getProtocols() {
+ return connectionFactory.getProtocols();
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint) {
+ final Connection connection = connectionFactory.newConnection(connector, endPoint);
+ connection.addEventListener(new Connection.Listener() {
+ private Timer.Context context;
+
+ @Override
+ public void onOpened(Connection connection) {
+ this.context = timer.time();
+ if (counter != null) {
+ counter.inc();
+ }
+ }
+
+ @Override
+ public void onClosed(Connection connection) {
+ context.stop();
+ if (counter != null) {
+ counter.dec();
+ }
+ }
+ });
+ return connection;
+ }
+}
diff --git a/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPool.java b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPool.java
new file mode 100644
index 0000000..9911737
--- /dev/null
+++ b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPool.java
@@ -0,0 +1,168 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
+ private static final String NAME_UTILIZATION = "utilization";
+ private static final String NAME_UTILIZATION_MAX = "utilization-max";
+ private static final String NAME_SIZE = "size";
+ private static final String NAME_JOBS = "jobs";
+ private static final String NAME_JOBS_QUEUE_UTILIZATION = "jobs-queue-utilization";
+
+ private final MetricRegistry metricRegistry;
+ private String prefix;
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry) {
+ this(registry, 200);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads) {
+ this(registry, maxThreads, 8);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads) {
+ this(registry, maxThreads, minThreads, 60000);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("queue") BlockingQueue<Runnable> queue) {
+ this(registry, maxThreads, minThreads, 60000, queue);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout) {
+ this(registry, maxThreads, minThreads, idleTimeout, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue<Runnable> queue) {
+ this(registry, maxThreads, minThreads, idleTimeout, queue, (ThreadGroup) 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("threadFactory") ThreadFactory threadFactory) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, null, threadFactory);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, threadGroup);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup) {
+ this(registry, maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup,
+ @Name("threadFactory") ThreadFactory threadFactory) {
+ this(registry, maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, threadFactory, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue<Runnable> queue,
+ @Name("threadGroup") ThreadGroup threadGroup,
+ @Name("threadFactory") ThreadFactory threadFactory,
+ @Name("prefix") String prefix) {
+ super(maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, threadFactory);
+ 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();
+
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.register(name(prefix, NAME_UTILIZATION), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(getThreads() - getIdleThreads(), getThreads());
+ }
+ });
+ metricRegistry.register(name(prefix, NAME_UTILIZATION_MAX), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(getThreads() - getIdleThreads(), getMaxThreads());
+ }
+ });
+ metricRegistry.registerGauge(name(prefix, NAME_SIZE), this::getThreads);
+ // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
+ // ArrayBlockingQueue for its queue, and is therefore a constant-time operation.
+ metricRegistry.registerGauge(name(prefix, NAME_JOBS), () -> getQueue().size());
+ metricRegistry.register(name(prefix, NAME_JOBS_QUEUE_UTILIZATION), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ BlockingQueue<Runnable> queue = getQueue();
+ return Ratio.of(queue.size(), queue.size() + queue.remainingCapacity());
+ }
+ });
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION));
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION_MAX));
+ metricRegistry.remove(name(prefix, NAME_SIZE));
+ metricRegistry.remove(name(prefix, NAME_JOBS));
+ metricRegistry.remove(name(prefix, NAME_JOBS_QUEUE_UTILIZATION));
+
+ super.doStop();
+ }
+
+ private String getMetricPrefix() {
+ return this.prefix == null ? name(QueuedThreadPool.class, getName()) : name(this.prefix, getName());
+ }
+}
diff --git a/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactoryTest.java b/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactoryTest.java
new file mode 100644
index 0000000..a988de2
--- /dev/null
+++ b/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactoryTest.java
@@ -0,0 +1,86 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Callback;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedConnectionFactoryTest {
+ private final MetricRegistry registry = new MetricRegistry();
+ private final Server server = new Server();
+ private final ServerConnector connector =
+ new ServerConnector(server, new InstrumentedConnectionFactory(new HttpConnectionFactory(),
+ registry.timer("http.connections"),
+ registry.counter("http.active-connections")));
+ private final HttpClient client = new HttpClient();
+
+ @Before
+ public void setUp() throws Exception {
+ server.setHandler(new Handler.Abstract() {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback) throws Exception {
+ Content.Sink.write(response, true, "OK", callback);
+ return true;
+ }
+ });
+
+ server.addConnector(connector);
+ server.start();
+
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void instrumentsConnectionTimes() throws Exception {
+ final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ client.stop(); // close the connection
+
+ Thread.sleep(100); // make sure the connection is closed
+
+ final Timer timer = registry.timer(MetricRegistry.name("http.connections"));
+ assertThat(timer.getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void instrumentsActiveConnections() throws Exception {
+ final Counter counter = registry.counter("http.active-connections");
+
+ final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertThat(counter.getCount())
+ .isEqualTo(1);
+
+ client.stop(); // close the connection
+
+ Thread.sleep(100); // make sure the connection is closed
+
+ assertThat(counter.getCount())
+ .isEqualTo(0);
+ }
+}
diff --git a/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPoolTest.java b/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPoolTest.java
new file mode 100644
index 0000000..5a4e4af
--- /dev/null
+++ b/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPoolTest.java
@@ -0,0 +1,49 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.MetricRegistry;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedQueuedThreadPoolTest {
+ private static final String PREFIX = "prefix";
+
+ private MetricRegistry metricRegistry;
+ private InstrumentedQueuedThreadPool iqtp;
+
+ @Before
+ public void setUp() {
+ metricRegistry = new MetricRegistry();
+ iqtp = new InstrumentedQueuedThreadPool(metricRegistry);
+ }
+
+ @Test
+ public void customMetricsPrefix() throws Exception {
+ iqtp.setPrefix(PREFIX);
+ iqtp.start();
+
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("Custom metrics prefix doesn't match")
+ .allSatisfy(name -> assertThat(name).startsWith(PREFIX));
+
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
+ }
+
+ @Test
+ public void metricsPrefixBackwardCompatible() throws Exception {
+ iqtp.start();
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .allSatisfy(name -> assertThat(name).startsWith(QueuedThreadPool.class.getName()));
+
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
+ }
+}
diff --git a/metrics-jetty8/pom.xml b/metrics-jetty8/pom.xml
deleted file mode 100644
index 1efeee1..0000000
--- a/metrics-jetty8/pom.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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.6</version>
- </parent>
-
- <artifactId>metrics-jetty8</artifactId>
- <name>Metrics Integration for Jetty 8</name>
- <packaging>bundle</packaging>
- <description>
- A set of extensions for Jetty 8 which provide instrumentation of thread pools, connector
- metrics, and application latency and utilization.
- </description>
-
- <dependencies>
- <dependency>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-server</artifactId>
- <version>${jetty8.version}</version>
- </dependency>
- </dependencies>
-</project>
diff --git a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedBlockingChannelConnector.java b/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedBlockingChannelConnector.java
deleted file mode 100644
index d554e16..0000000
--- a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedBlockingChannelConnector.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.codahale.metrics.jetty8;
-
-import com.codahale.metrics.*;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.server.nio.BlockingChannelConnector;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-public class InstrumentedBlockingChannelConnector extends BlockingChannelConnector {
- private final Timer duration;
- private final Meter accepts, connects, disconnects;
- private final Counter connections;
- private final Clock clock;
-
- public InstrumentedBlockingChannelConnector(MetricRegistry registry,
- int port,
- Clock clock) {
- super();
- this.clock = clock;
- setPort(port);
- this.duration = registry.timer(name(BlockingChannelConnector.class,
- Integer.toString(port),
- "connection-duration"));
- this.accepts = registry.meter(name(BlockingChannelConnector.class,
- Integer.toString(port),
- "accepts"));
- this.connects = registry.meter(name(BlockingChannelConnector.class,
- Integer.toString(port),
- "connects"));
- this.disconnects = registry.meter(name(BlockingChannelConnector.class,
- Integer.toString(port),
- "disconnects"));
- this.connections = registry.counter(name(BlockingChannelConnector.class,
- Integer.toString(port),
- "active-connections"));
- }
-
- @Override
- public void accept(int acceptorID) throws IOException, InterruptedException {
- super.accept(acceptorID);
- accepts.mark();
- }
-
- @Override
- protected void connectionOpened(Connection connection) {
- connections.inc();
- super.connectionOpened(connection);
- connects.mark();
- }
-
- @Override
- protected void connectionClosed(Connection connection) {
- super.connectionClosed(connection);
- disconnects.mark();
- final long duration = clock.getTime() - connection.getTimeStamp();
- this.duration.update(duration, TimeUnit.MILLISECONDS);
- connections.dec();
- }
-}
diff --git a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedHandler.java b/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedHandler.java
deleted file mode 100644
index f2cc8a0..0000000
--- a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedHandler.java
+++ /dev/null
@@ -1,248 +0,0 @@
-package com.codahale.metrics.jetty8;
-
-import com.codahale.metrics.*;
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.server.AsyncContinuation;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.HandlerWrapper;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import static com.codahale.metrics.MetricRegistry.name;
-import static org.eclipse.jetty.http.HttpMethods.*;
-
-/**
- * A Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
- * instance.
- */
-public class InstrumentedHandler extends HandlerWrapper {
- private static final String PATCH = "PATCH";
-
- private final Timer dispatches;
- private final Meter requests;
- private final Meter resumes;
- private final Meter suspends;
- private final Meter expires;
-
- private final Counter activeRequests;
- private final Counter activeSuspendedRequests;
- private final Counter activeDispatches;
-
- private final Meter[] responses;
-
- private final Timer getRequests, postRequests, headRequests,
- putRequests, deleteRequests, optionsRequests, traceRequests,
- connectRequests, patchRequests, otherRequests;
-
- private final ContinuationListener listener;
-
- /**
- * Create a new instrumented handler using a given metrics registry. The name of the metric will
- * be derived from the class of the Handler.
- *
- * @param registry the registry for the metrics
- * @param underlying the handler about which metrics will be collected
- */
- public InstrumentedHandler(MetricRegistry registry, Handler underlying) {
- this(registry, underlying, name(underlying.getClass()));
- }
-
- /**
- * Create a new instrumented handler using a given metrics registry and a custom prefix.
- *
- * @param registry the registry for the metrics
- * @param underlying the handler about which metrics will be collected
- * @param prefix the prefix to use for the metrics names
- */
- public InstrumentedHandler(MetricRegistry registry, Handler underlying, String prefix) {
- super();
- this.dispatches = registry.timer(name(prefix, "dispatches"));
- this.requests = registry.meter(name(prefix, "requests"));
- this.resumes = registry.meter(name(prefix, "resumes"));
- this.suspends = registry.meter(name(prefix, "suspends"));
- this.expires = registry.meter(name(prefix, "expires"));
-
- this.activeRequests = registry.counter(name(prefix, "active-requests"));
- this.activeSuspendedRequests = registry.counter(name(prefix,
- "active-suspended-requests"));
- this.activeDispatches = registry.counter(name(prefix, "active-dispatches"));
-
- this.responses = new Meter[]{
- registry.meter(name(prefix, "1xx-responses")), // 1xx
- registry.meter(name(prefix, "2xx-responses")), // 2xx
- registry.meter(name(prefix, "3xx-responses")), // 3xx
- registry.meter(name(prefix, "4xx-responses")), // 4xx
- registry.meter(name(prefix, "5xx-responses")) // 5xx
- };
-
- registry.register(name(prefix, "percent-4xx-1m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getOneMinuteRate(),
- requests.getOneMinuteRate());
- }
- });
-
- registry.register(name(prefix, "percent-4xx-5m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getFiveMinuteRate(),
- requests.getFiveMinuteRate());
- }
- });
-
- registry.register(name(prefix, "percent-4xx-15m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getFifteenMinuteRate(),
- requests.getFifteenMinuteRate());
- }
- });
-
- registry.register(name(prefix, "percent-5xx-1m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getOneMinuteRate(),
- requests.getOneMinuteRate());
- }
- });
-
- registry.register(name(prefix, "percent-5xx-5m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getFiveMinuteRate(),
- requests.getFiveMinuteRate());
- }
- });
-
- registry.register(name(prefix, "percent-5xx-15m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getFifteenMinuteRate(),
- requests.getFifteenMinuteRate());
- }
- });
-
- this.listener = new ContinuationListener() {
- @Override
- public void onComplete(Continuation continuation) {
- final Request request = ((AsyncContinuation) continuation).getBaseRequest();
- updateResponses(request);
- if (!continuation.isResumed()) {
- activeSuspendedRequests.dec();
- }
- expires.mark();
- }
-
- @Override
- public void onTimeout(Continuation continuation) {
- final Request request = ((AsyncContinuation) continuation).getBaseRequest();
- updateResponses(request);
- if (!continuation.isResumed()) {
- activeSuspendedRequests.dec();
- }
- }
- };
-
- this.getRequests = registry.timer(name(prefix, "get-requests"));
- this.postRequests = registry.timer(name(prefix, "post-requests"));
- this.headRequests = registry.timer(name(prefix, "head-requests"));
- this.putRequests = registry.timer(name(prefix, "put-requests"));
- this.deleteRequests = registry.timer(name(prefix, "delete-requests"));
- this.optionsRequests = registry.timer(name(prefix, "options-requests"));
- this.traceRequests = registry.timer(name(prefix, "trace-requests"));
- this.connectRequests = registry.timer(name(prefix, "connect-requests"));
- this.patchRequests = registry.timer(name(prefix, "patch-requests"));
- this.otherRequests = registry.timer(name(prefix, "other-requests"));
-
- setHandler(underlying);
- }
-
- @Override
- public void handle(String target, Request request,
- HttpServletRequest httpRequest, HttpServletResponse httpResponse)
- throws IOException, ServletException {
- activeDispatches.inc();
-
- final AsyncContinuation continuation = request.getAsyncContinuation();
-
- final long start;
- final boolean isMilliseconds;
-
- if (continuation.isInitial()) {
- activeRequests.inc();
- start = request.getTimeStamp();
- isMilliseconds = true;
- } else {
- activeSuspendedRequests.dec();
- if (continuation.isResumed()) {
- resumes.mark();
- }
- isMilliseconds = false;
- start = System.nanoTime();
- }
-
- try {
- super.handle(target, request, httpRequest, httpResponse);
- } finally {
- if (isMilliseconds) {
- final long duration = System.currentTimeMillis() - start;
- dispatches.update(duration, TimeUnit.MILLISECONDS);
- requestTimer(request.getMethod()).update(duration, TimeUnit.MILLISECONDS);
- } else {
- final long duration = System.nanoTime() - start;
- dispatches.update(duration, TimeUnit.NANOSECONDS);
- requestTimer(request.getMethod()).update(duration, TimeUnit.NANOSECONDS);
- }
-
- activeDispatches.dec();
- if (continuation.isSuspended()) {
- if (continuation.isInitial()) {
- continuation.addContinuationListener(listener);
- }
- suspends.mark();
- activeSuspendedRequests.inc();
- } else if (continuation.isInitial()) {
- updateResponses(request);
- }
- }
- }
-
- private Timer requestTimer(String method) {
- if (GET.equalsIgnoreCase(method)) {
- return getRequests;
- } else if (POST.equalsIgnoreCase(method)) {
- return postRequests;
- } else if (PUT.equalsIgnoreCase(method)) {
- return putRequests;
- } else if (HEAD.equalsIgnoreCase(method)) {
- return headRequests;
- } else if (DELETE.equalsIgnoreCase(method)) {
- return deleteRequests;
- } else if (OPTIONS.equalsIgnoreCase(method)) {
- return optionsRequests;
- } else if (TRACE.equalsIgnoreCase(method)) {
- return traceRequests;
- } else if (CONNECT.equalsIgnoreCase(method)) {
- return connectRequests;
- } else if (PATCH.equalsIgnoreCase(method)) {
- return patchRequests;
- }
- return otherRequests;
- }
-
- private void updateResponses(Request request) {
- final int response = request.getResponse().getStatus() / 100;
- if (response >= 1 && response <= 5) {
- responses[response - 1].mark();
- }
- activeRequests.dec();
- requests.mark();
- }
-}
diff --git a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedQueuedThreadPool.java b/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedQueuedThreadPool.java
deleted file mode 100644
index aef0786..0000000
--- a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedQueuedThreadPool.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.codahale.metrics.jetty8;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.RatioGauge;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
- public InstrumentedQueuedThreadPool(MetricRegistry registry) {
- super();
- registry.register(name(QueuedThreadPool.class, "percent-idle"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(getIdleThreads(),
- getThreads());
- }
- });
- registry.register(name(QueuedThreadPool.class, "active-threads"), new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- return getThreads();
- }
- });
- registry.register(name(QueuedThreadPool.class, "idle-threads"), new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- return getIdleThreads();
- }
- });
- registry.register(name(QueuedThreadPool.class, "jobs"), new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
- // ArrayBlockingQueue for its queue, and is therefore a constant-time operation.
- return getQueue() != null ? getQueue().size() : 0;
- }
- });
- registry.register(name(QueuedThreadPool.class, "utilization-max"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(getThreads() - getIdleThreads(), getMaxThreads());
- }
- });
- }
-}
diff --git a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSelectChannelConnector.java b/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSelectChannelConnector.java
deleted file mode 100644
index ab494be..0000000
--- a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSelectChannelConnector.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.codahale.metrics.jetty8;
-
-import com.codahale.metrics.*;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-public class InstrumentedSelectChannelConnector extends SelectChannelConnector {
- private final Timer duration;
- private final Meter accepts, connects, disconnects;
- private final Counter connections;
- private final Clock clock;
-
- public InstrumentedSelectChannelConnector(MetricRegistry registry,
- int port,
- Clock clock) {
- super();
- this.clock = clock;
- setPort(port);
-
- this.duration = registry.timer(name(SelectChannelConnector.class,
- Integer.toString(port),
- "connection-duration"));
- this.accepts = registry.meter(name(SelectChannelConnector.class,
- Integer.toString(port),
- "accepts"));
- this.connects = registry.meter(name(SelectChannelConnector.class,
- Integer.toString(port),
- "connects"));
- this.disconnects = registry.meter(name(SelectChannelConnector.class,
- Integer.toString(port),
- "disconnects"));
- this.connections = registry.counter(name(SelectChannelConnector.class,
- Integer.toString(port),
- "active-connections"));
- }
-
- @Override
- public void accept(int acceptorID) throws IOException {
- super.accept(acceptorID);
- accepts.mark();
- }
-
- @Override
- protected void connectionOpened(Connection connection) {
- connections.inc();
- super.connectionOpened(connection);
- connects.mark();
- }
-
- @Override
- protected void connectionClosed(Connection connection) {
- super.connectionClosed(connection);
- disconnects.mark();
- final long duration = clock.getTime() - connection.getTimeStamp();
- this.duration.update(duration, TimeUnit.MILLISECONDS);
- connections.dec();
- }
-}
diff --git a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSocketConnector.java b/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSocketConnector.java
deleted file mode 100644
index 233f049..0000000
--- a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSocketConnector.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.codahale.metrics.jetty8;
-
-import com.codahale.metrics.*;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.server.bio.SocketConnector;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-public class InstrumentedSocketConnector extends SocketConnector {
- private final Timer duration;
- private final Meter accepts, connects, disconnects;
- private final Counter connections;
- private final Clock clock;
-
- public InstrumentedSocketConnector(MetricRegistry registry,
- int port,
- Clock clock) {
- super();
- this.clock = clock;
- setPort(port);
- this.duration = registry.timer(name(SocketConnector.class,
- Integer.toString(port),
- "connection-duration"));
- this.accepts = registry.meter(name(SocketConnector.class,
- Integer.toString(port),
- "accepts"));
- this.connects = registry.meter(name(SocketConnector.class,
- Integer.toString(port),
- "connects"));
- this.disconnects = registry.meter(name(SocketConnector.class,
- Integer.toString(port),
- "disconnects"));
- this.connections = registry.counter(name(SocketConnector.class,
- Integer.toString(port),
- "active-connections"));
- }
-
- @Override
- public void accept(int acceptorID) throws IOException, InterruptedException {
- super.accept(acceptorID);
- accepts.mark();
- }
-
- @Override
- protected void connectionOpened(Connection connection) {
- connections.inc();
- super.connectionOpened(connection);
- connects.mark();
- }
-
- @Override
- protected void connectionClosed(Connection connection) {
- super.connectionClosed(connection);
- disconnects.mark();
- final long duration = clock.getTime() - connection.getTimeStamp();
- this.duration.update(duration, TimeUnit.MILLISECONDS);
- connections.dec();
- }
-}
diff --git a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSslSelectChannelConnector.java b/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSslSelectChannelConnector.java
deleted file mode 100644
index 21c2f15..0000000
--- a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSslSelectChannelConnector.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.codahale.metrics.jetty8;
-
-import com.codahale.metrics.*;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-public class InstrumentedSslSelectChannelConnector extends SslSelectChannelConnector {
- private final Timer duration;
- private final Meter accepts, connects, disconnects;
- private final Counter connections;
- private final Clock clock;
-
- public InstrumentedSslSelectChannelConnector(MetricRegistry registry,
- int port,
- SslContextFactory factory,
- Clock clock) {
- super(factory);
- this.clock = clock;
- setPort(port);
- this.duration = registry.timer(name(SslSelectChannelConnector.class,
- Integer.toString(port),
- "connection-duration"));
- this.accepts = registry.meter(name(SslSelectChannelConnector.class,
- Integer.toString(port),
- "accepts"));
- this.connects = registry.meter(name(SslSelectChannelConnector.class,
- Integer.toString(port),
- "connects"));
- this.disconnects = registry.meter(name(SslSelectChannelConnector.class,
- Integer.toString(port),
- "disconnects"));
- this.connections = registry.counter(name(SslSelectChannelConnector.class,
- Integer.toString(port),
- "active-connections"));
-
- }
-
- @Override
- public void accept(int acceptorID) throws IOException {
- super.accept(acceptorID);
- accepts.mark();
- }
-
- @Override
- protected void connectionOpened(Connection connection) {
- connections.inc();
- super.connectionOpened(connection);
- connects.mark();
- }
-
- @Override
- protected void connectionClosed(Connection connection) {
- super.connectionClosed(connection);
- disconnects.mark();
- final long duration = clock.getTime() - connection.getTimeStamp();
- this.duration.update(duration, TimeUnit.MILLISECONDS);
- connections.dec();
- }
-}
diff --git a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSslSocketConnector.java b/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSslSocketConnector.java
deleted file mode 100644
index 5182121..0000000
--- a/metrics-jetty8/src/main/java/com/codahale/metrics/jetty8/InstrumentedSslSocketConnector.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.codahale.metrics.jetty8;
-
-import com.codahale.metrics.*;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.server.ssl.SslSocketConnector;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-public class InstrumentedSslSocketConnector extends SslSocketConnector {
- private final Timer duration;
- private final Meter accepts, connects, disconnects;
- private final Counter connections;
- private final Clock clock;
-
- public InstrumentedSslSocketConnector(MetricRegistry registry,
- int port,
- SslContextFactory factory,
- Clock clock) {
- super(factory);
- this.clock = clock;
- setPort(port);
- this.duration = registry.timer(name(SslSocketConnector.class,
- Integer.toString(port),
- "connection-duration"));
- this.accepts = registry.meter(name(SslSocketConnector.class,
- Integer.toString(port),
- "accepts"));
- this.connects = registry.meter(name(SslSocketConnector.class,
- Integer.toString(port),
- "connects"));
- this.disconnects = registry.meter(name(SslSocketConnector.class,
- Integer.toString(port),
- "disconnects"));
- this.connections = registry.counter(name(SslSocketConnector.class,
- Integer.toString(port),
- "active-connections"));
- }
-
- @Override
- public void accept(int acceptorID) throws IOException, InterruptedException {
- super.accept(acceptorID);
- accepts.mark();
- }
-
- @Override
- protected void connectionOpened(Connection connection) {
- connections.inc();
- super.connectionOpened(connection);
- connects.mark();
- }
-
- @Override
- protected void connectionClosed(Connection connection) {
- super.connectionClosed(connection);
- disconnects.mark();
- final long duration = clock.getTime() - connection.getTimeStamp();
- this.duration.update(duration, TimeUnit.MILLISECONDS);
- connections.dec();
- }
-}
diff --git a/metrics-jetty9-legacy/pom.xml b/metrics-jetty9-legacy/pom.xml
deleted file mode 100644
index 6a4f88c..0000000
--- a/metrics-jetty9-legacy/pom.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?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.6</version>
- </parent>
-
- <artifactId>metrics-jetty9-legacy</artifactId>
- <name>Metrics Integration for Jetty 9.0</name>
- <packaging>bundle</packaging>
- <description>
- A set of extensions for Jetty 9.0 which provide instrumentation of thread pools, connector
- metrics, and application latency and utilization.
- </description>
-
- <dependencies>
- <dependency>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-server</artifactId>
- <version>${jetty9.legacy.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-client</artifactId>
- <version>${jetty9.legacy.version}</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.1</version>
- <configuration>
- <source>1.7</source>
- <target>1.7</target>
- </configuration>
- </plugin>
- </plugins>
- </build>
-</project>
diff --git a/metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedHandler.java b/metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedHandler.java
deleted file mode 100644
index 80082c9..0000000
--- a/metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedHandler.java
+++ /dev/null
@@ -1,300 +0,0 @@
-package com.codahale.metrics.jetty9;
-
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.RatioGauge;
-import com.codahale.metrics.Timer;
-import org.eclipse.jetty.http.HttpMethod;
-import org.eclipse.jetty.server.AsyncContextState;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.HttpChannelState;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.HandlerWrapper;
-
-import javax.servlet.AsyncEvent;
-import javax.servlet.AsyncListener;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-/**
- * A Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
- * instance.
- */
-public class InstrumentedHandler extends HandlerWrapper {
- private final MetricRegistry metricRegistry;
-
- private String name;
- private final String prefix;
-
- // the requests handled by this handler, excluding active
- private Timer requests;
-
- // the number of dispatches seen by this handler, excluding active
- private Timer dispatches;
-
- // the number of active requests
- private Counter activeRequests;
-
- // the number of active dispatches
- private Counter activeDispatches;
-
- // the number of requests currently suspended.
- private Counter activeSuspended;
-
- // the number of requests that have been asynchronously dispatched
- private Meter asyncDispatches;
-
- // the number of requests that expired while suspended
- private Meter asyncTimeouts;
-
- private Meter[] responses;
-
- private Timer getRequests;
- private Timer postRequests;
- private Timer headRequests;
- private Timer putRequests;
- private Timer deleteRequests;
- private Timer optionsRequests;
- private Timer traceRequests;
- private Timer connectRequests;
- private Timer moveRequests;
- private Timer otherRequests;
-
- private AsyncListener listener;
-
- /**
- * Create a new instrumented handler using a given metrics registry.
- *
- * @param registry the registry for the metrics
- *
- */
- public InstrumentedHandler(MetricRegistry registry) {
- this(registry, null);
- }
-
- /**
- * Create a new instrumented handler using a given metrics registry.
- *
- * @param registry the registry for the metrics
- * @param prefix the prefix to use for the metrics names
- *
- */
- public InstrumentedHandler(MetricRegistry registry, String prefix) {
- this.metricRegistry = registry;
- this.prefix = prefix;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- @Override
- protected void doStart() throws Exception {
- super.doStart();
-
- final String prefix = this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name);
-
- this.requests = metricRegistry.timer(name(prefix, "requests"));
- this.dispatches = metricRegistry.timer(name(prefix, "dispatches"));
-
- this.activeRequests = metricRegistry.counter(name(prefix, "active-requests"));
- this.activeDispatches = metricRegistry.counter(name(prefix, "active-dispatches"));
- this.activeSuspended = metricRegistry.counter(name(prefix, "active-suspended"));
-
- this.asyncDispatches = metricRegistry.meter(name(prefix, "async-dispatches"));
- this.asyncTimeouts = metricRegistry.meter(name(prefix, "async-timeouts"));
-
- this.responses = new Meter[]{
- metricRegistry.meter(name(prefix, "1xx-responses")), // 1xx
- metricRegistry.meter(name(prefix, "2xx-responses")), // 2xx
- metricRegistry.meter(name(prefix, "3xx-responses")), // 3xx
- metricRegistry.meter(name(prefix, "4xx-responses")), // 4xx
- metricRegistry.meter(name(prefix, "5xx-responses")) // 5xx
- };
-
- this.getRequests = metricRegistry.timer(name(prefix, "get-requests"));
- this.postRequests = metricRegistry.timer(name(prefix, "post-requests"));
- this.headRequests = metricRegistry.timer(name(prefix, "head-requests"));
- this.putRequests = metricRegistry.timer(name(prefix, "put-requests"));
- this.deleteRequests = metricRegistry.timer(name(prefix, "delete-requests"));
- this.optionsRequests = metricRegistry.timer(name(prefix, "options-requests"));
- this.traceRequests = metricRegistry.timer(name(prefix, "trace-requests"));
- this.connectRequests = metricRegistry.timer(name(prefix, "connect-requests"));
- this.moveRequests = metricRegistry.timer(name(prefix, "move-requests"));
- this.otherRequests = metricRegistry.timer(name(prefix, "other-requests"));
-
- metricRegistry.register(name(prefix, "percent-4xx-1m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getOneMinuteRate(),
- requests.getOneMinuteRate());
- }
- });
-
- metricRegistry.register(name(prefix, "percent-4xx-5m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getFiveMinuteRate(),
- requests.getFiveMinuteRate());
- }
- });
-
- metricRegistry.register(name(prefix, "percent-4xx-15m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getFifteenMinuteRate(),
- requests.getFifteenMinuteRate());
- }
- });
-
- metricRegistry.register(name(prefix, "percent-5xx-1m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getOneMinuteRate(),
- requests.getOneMinuteRate());
- }
- });
-
- metricRegistry.register(name(prefix, "percent-5xx-5m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getFiveMinuteRate(),
- requests.getFiveMinuteRate());
- }
- });
-
- metricRegistry.register(name(prefix, "percent-5xx-15m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getFifteenMinuteRate(),
- requests.getFifteenMinuteRate());
- }
- });
-
-
- this.listener = new AsyncListener() {
- private long startTime;
-
- @Override
- public void onTimeout(AsyncEvent event) throws IOException {
- asyncTimeouts.mark();
- }
-
- @Override
- public void onStartAsync(AsyncEvent event) throws IOException {
- startTime = System.currentTimeMillis();
- event.getAsyncContext().addListener(this);
- }
-
- @Override
- public void onError(AsyncEvent event) throws IOException {
- }
-
- @Override
- public void onComplete(AsyncEvent event) throws IOException {
- final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
- final HttpServletRequest request = (HttpServletRequest) state.getRequest();
- final HttpServletResponse response = (HttpServletResponse) state.getResponse();
- updateResponses(request, response, startTime);
- if (!state.getHttpChannelState().isDispatched()) {
- activeSuspended.dec();
- }
- }
- };
- }
-
- @Override
- public void handle(String path,
- Request request,
- HttpServletRequest httpRequest,
- HttpServletResponse httpResponse) throws IOException, ServletException {
-
- activeDispatches.inc();
-
- final long start;
- final HttpChannelState state = request.getHttpChannelState();
- if (state.isInitial()) {
- // new request
- activeRequests.inc();
- start = request.getTimeStamp();
- } else {
- // resumed request
- start = System.currentTimeMillis();
- activeSuspended.dec();
- if (state.isDispatched()) {
- asyncDispatches.mark();
- }
- }
-
- try {
- super.handle(path, request, httpRequest, httpResponse);
- } finally {
- final long now = System.currentTimeMillis();
- final long dispatched = now - start;
-
- activeDispatches.dec();
- dispatches.update(dispatched, TimeUnit.MILLISECONDS);
-
- if (state.isSuspended()) {
- if (state.isInitial()) {
- state.addListener(listener);
- }
- activeSuspended.inc();
- } else if (state.isInitial()) {
- updateResponses(httpRequest, httpResponse, start);
- }
- // else onCompletion will handle it.
- }
- }
-
- private Timer requestTimer(String method) {
- final HttpMethod m = HttpMethod.fromString(method);
- if (m == null) {
- return otherRequests;
- } else {
- switch (m) {
- case GET:
- return getRequests;
- case POST:
- return postRequests;
- case PUT:
- return putRequests;
- case HEAD:
- return headRequests;
- case DELETE:
- return deleteRequests;
- case OPTIONS:
- return optionsRequests;
- case TRACE:
- return traceRequests;
- case CONNECT:
- return connectRequests;
- case MOVE:
- return moveRequests;
- default:
- return otherRequests;
- }
- }
- }
-
- private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start) {
- final int responseStatus = response.getStatus() / 100;
- if (responseStatus >= 1 && responseStatus <= 5) {
- responses[responseStatus - 1].mark();
- }
- activeRequests.dec();
- final long elapsedTime = System.currentTimeMillis() - start;
- requests.update(elapsedTime, TimeUnit.MILLISECONDS);
- requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
- }
-}
diff --git a/metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPool.java b/metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPool.java
deleted file mode 100644
index ac4114d..0000000
--- a/metrics-jetty9-legacy/src/main/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPool.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.codahale.metrics.jetty9;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.RatioGauge;
-import org.eclipse.jetty.util.annotation.Name;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
-import java.util.concurrent.BlockingQueue;
-
-import static com.codahale.metrics.MetricRegistry.name;
-
-public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
- private final MetricRegistry metricRegistry;
-
- public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry) {
- this(registry, 200);
- }
-
- public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
- @Name("maxThreads") int maxThreads) {
- this(registry, maxThreads, 8);
- }
-
- public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
- @Name("maxThreads") int maxThreads,
- @Name("minThreads") int minThreads) {
- this(registry, maxThreads, minThreads, 60000);
- }
-
- public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
- @Name("maxThreads") int maxThreads,
- @Name("minThreads") int minThreads,
- @Name("idleTimeout") int idleTimeout) {
- this(registry, maxThreads, minThreads, idleTimeout, null);
- }
-
- public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
- @Name("maxThreads") int maxThreads,
- @Name("minThreads") int minThreads,
- @Name("idleTimeout") int idleTimeout,
- @Name("queue") BlockingQueue<Runnable> queue) {
- super(maxThreads, minThreads, idleTimeout, queue);
- this.metricRegistry = registry;
- }
-
- @Override
- protected void doStart() throws Exception {
- super.doStart();
- metricRegistry.register(name(QueuedThreadPool.class, getName(), "utilization"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(getThreads() - getIdleThreads(), getThreads());
- }
- });
- metricRegistry.register(name(QueuedThreadPool.class, getName(), "utilization-max"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(getThreads() - getIdleThreads(), getMaxThreads());
- }
- });
- metricRegistry.register(name(QueuedThreadPool.class, getName(), "size"), new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- return getThreads();
- }
- });
- metricRegistry.register(name(QueuedThreadPool.class, getName(), "jobs"), new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
- // ArrayBlockingQueue for its queue, and is therefore a constant-time operation.
- return getQueue().size();
- }
- });
- }
-}
diff --git a/metrics-jetty9-legacy/src/test/java/com/codahale/metrics/jetty9/InstrumentedHandlerTest.java b/metrics-jetty9-legacy/src/test/java/com/codahale/metrics/jetty9/InstrumentedHandlerTest.java
deleted file mode 100644
index 9b7c86b..0000000
--- a/metrics-jetty9-legacy/src/test/java/com/codahale/metrics/jetty9/InstrumentedHandlerTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-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.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class InstrumentedHandlerTest {
- private final HttpClient client = new HttpClient();
- private final MetricRegistry registry = new MetricRegistry();
- private final Server server = new Server();
- private final ServerConnector connector = new ServerConnector(server);
- private final InstrumentedHandler handler = new InstrumentedHandler(registry);
-
- @Before
- public void setUp() throws Exception {
- handler.setName("handler");
- handler.setHandler(new DefaultHandler());
- server.addConnector(connector);
- server.setHandler(handler);
- server.start();
- client.start();
- }
-
- @After
- public void tearDown() throws Exception {
- server.stop();
- client.stop();
- }
-
- @Test
- public void hasAName() throws Exception {
- assertThat(handler.getName())
- .isEqualTo("handler");
- }
-
- @Test
- public void createsMetricsForTheHandler() throws Exception {
- final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/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"
- );
- }
-}
diff --git a/metrics-jetty9/pom.xml b/metrics-jetty9/pom.xml
index 66cd76e..00fae98 100644
--- a/metrics-jetty9/pom.xml
+++ b/metrics-jetty9/pom.xml
@@ -5,47 +5,92 @@
<parent>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-parent</artifactId>
- <version>3.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-jetty9</artifactId>
- <name>Metrics Integration for Jetty 9.1 and higher</name>
+ <name>Metrics Integration for Jetty 9.3 and higher</name>
<packaging>bundle</packaging>
<description>
- A set of extensions for Jetty 9.1 and higher which provide instrumentation of thread pools, connector
+ A set of extensions for Jetty 9.3 and higher which provide instrumentation of thread pools, connector
metrics, and application latency and utilization.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.jetty9</javaModuleName>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-bom</artifactId>
+ <version>${jetty9.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-annotation</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
- <version>${jetty9.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
- <version>${jetty9.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.1</version>
- <configuration>
- <source>1.7</source>
- <target>1.7</target>
- </configuration>
- </plugin>
- </plugins>
- </build>
</project>
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 ce218e7..1d64a3e 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
@@ -1,5 +1,6 @@
package com.codahale.metrics.jetty9;
+import com.codahale.metrics.Counter;
import com.codahale.metrics.Timer;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
@@ -7,25 +8,22 @@ 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;
+ private final Counter counter;
public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer) {
+ this(connectionFactory, timer, null);
+ }
+
+ public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer, Counter counter) {
this.connectionFactory = connectionFactory;
this.timer = timer;
+ this.counter = counter;
addBean(connectionFactory);
- try {
- getProtocols = connectionFactory.getClass().getMethod("getProtocols");
- } catch (NoSuchMethodException ignore) {
- getProtocols = null;
- }
}
@Override
@@ -33,15 +31,9 @@ public class InstrumentedConnectionFactory extends ContainerLifeCycle implements
return connectionFactory.getProtocol();
}
- @SuppressWarnings("unchecked")
+ @Override
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);
- }
+ return connectionFactory.getProtocols();
}
@Override
@@ -53,11 +45,17 @@ public class InstrumentedConnectionFactory extends ContainerLifeCycle implements
@Override
public void onOpened(Connection connection) {
this.context = timer.time();
+ if (counter != null) {
+ counter.inc();
+ }
}
@Override
public void onClosed(Connection connection) {
context.stop();
+ if (counter != null) {
+ counter.dec();
+ }
}
});
return connection;
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 0543fe9..2447161 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
@@ -5,6 +5,7 @@ import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.RatioGauge;
import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.AsyncContextState;
import org.eclipse.jetty.server.Handler;
@@ -18,15 +19,56 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
/**
* A Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
* instance.
*/
public class InstrumentedHandler extends HandlerWrapper {
+ private static final String NAME_REQUESTS = "requests";
+ private static final String NAME_DISPATCHES = "dispatches";
+ private static final String NAME_ACTIVE_REQUESTS = "active-requests";
+ private static final String NAME_ACTIVE_DISPATCHES = "active-dispatches";
+ private static final String NAME_ACTIVE_SUSPENDED = "active-suspended";
+ private static final String NAME_ASYNC_DISPATCHES = "async-dispatches";
+ private static final String NAME_ASYNC_TIMEOUTS = "async-timeouts";
+ private static final String NAME_1XX_RESPONSES = "1xx-responses";
+ private static final String NAME_2XX_RESPONSES = "2xx-responses";
+ private static final String NAME_3XX_RESPONSES = "3xx-responses";
+ private static final String NAME_4XX_RESPONSES = "4xx-responses";
+ private static final String NAME_5XX_RESPONSES = "5xx-responses";
+ private static final String NAME_GET_REQUESTS = "get-requests";
+ private static final String NAME_POST_REQUESTS = "post-requests";
+ private static final String NAME_HEAD_REQUESTS = "head-requests";
+ private static final String NAME_PUT_REQUESTS = "put-requests";
+ private static final String NAME_DELETE_REQUESTS = "delete-requests";
+ private static final String NAME_OPTIONS_REQUESTS = "options-requests";
+ private static final String NAME_TRACE_REQUESTS = "trace-requests";
+ private static final String NAME_CONNECT_REQUESTS = "connect-requests";
+ private static final String NAME_MOVE_REQUESTS = "move-requests";
+ private static final String NAME_OTHER_REQUESTS = "other-requests";
+ private static final String NAME_PERCENT_4XX_1M = "percent-4xx-1m";
+ private static final String NAME_PERCENT_4XX_5M = "percent-4xx-5m";
+ private static final String NAME_PERCENT_4XX_15M = "percent-4xx-15m";
+ private static final String NAME_PERCENT_5XX_1M = "percent-5xx-1m";
+ private static final String NAME_PERCENT_5XX_5M = "percent-5xx-5m";
+ private static final String NAME_PERCENT_5XX_15M = "percent-5xx-15m";
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+
private final MetricRegistry metricRegistry;
private String name;
@@ -53,7 +95,9 @@ public class InstrumentedHandler extends HandlerWrapper {
// the number of requests that expired while suspended
private Meter asyncTimeouts;
- private Meter[] responses;
+ private final ResponseMeteredLevel responseMeteredLevel;
+ private List<Meter> responses;
+ private Map<Integer, Meter> responseCodeMeters;
private Timer getRequests;
private Timer postRequests;
@@ -68,27 +112,45 @@ public class InstrumentedHandler extends HandlerWrapper {
private AsyncListener listener;
+ private HttpChannelState.State DISPATCHED_HACK;
+
/**
* Create a new instrumented handler using a given metrics registry.
*
- * @param registry the registry for the metrics
- *
+ * @param registry the registry for the metrics
*/
public InstrumentedHandler(MetricRegistry registry) {
this(registry, null);
}
- /**
- * Create a new instrumented handler using a given metrics registry.
- *
- * @param registry the registry for the metrics
- * @param prefix the prefix to use for the metrics names
- *
- */
- public InstrumentedHandler(MetricRegistry registry, String prefix) {
- this.metricRegistry = registry;
- this.prefix = prefix;
- }
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ */
+ public InstrumentedHandler(MetricRegistry registry, String prefix) {
+ this(registry, prefix, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param prefix the prefix to use for the metrics names
+ * @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
+ */
+ public InstrumentedHandler(MetricRegistry registry, String prefix, ResponseMeteredLevel responseMeteredLevel) {
+ this.metricRegistry = registry;
+ this.prefix = prefix;
+ this.responseMeteredLevel = responseMeteredLevel;
+
+ try {
+ DISPATCHED_HACK = HttpChannelState.State.valueOf("HANDLING");
+ } catch (IllegalArgumentException e) {
+ DISPATCHED_HACK = HttpChannelState.State.valueOf("DISPATCHED");
+ }
+ }
public String getName() {
return name;
@@ -102,115 +164,134 @@ public class InstrumentedHandler extends HandlerWrapper {
protected void doStart() throws Exception {
super.doStart();
- final String prefix = this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name);
-
- this.requests = metricRegistry.timer(name(prefix, "requests"));
- this.dispatches = metricRegistry.timer(name(prefix, "dispatches"));
-
- this.activeRequests = metricRegistry.counter(name(prefix, "active-requests"));
- this.activeDispatches = metricRegistry.counter(name(prefix, "active-dispatches"));
- this.activeSuspended = metricRegistry.counter(name(prefix, "active-suspended"));
-
- this.asyncDispatches = metricRegistry.meter(name(prefix, "async-dispatches"));
- this.asyncTimeouts = metricRegistry.meter(name(prefix, "async-timeouts"));
-
- this.responses = new Meter[]{
- metricRegistry.meter(name(prefix, "1xx-responses")), // 1xx
- metricRegistry.meter(name(prefix, "2xx-responses")), // 2xx
- metricRegistry.meter(name(prefix, "3xx-responses")), // 3xx
- metricRegistry.meter(name(prefix, "4xx-responses")), // 4xx
- metricRegistry.meter(name(prefix, "5xx-responses")) // 5xx
- };
-
- this.getRequests = metricRegistry.timer(name(prefix, "get-requests"));
- this.postRequests = metricRegistry.timer(name(prefix, "post-requests"));
- this.headRequests = metricRegistry.timer(name(prefix, "head-requests"));
- this.putRequests = metricRegistry.timer(name(prefix, "put-requests"));
- this.deleteRequests = metricRegistry.timer(name(prefix, "delete-requests"));
- this.optionsRequests = metricRegistry.timer(name(prefix, "options-requests"));
- this.traceRequests = metricRegistry.timer(name(prefix, "trace-requests"));
- this.connectRequests = metricRegistry.timer(name(prefix, "connect-requests"));
- this.moveRequests = metricRegistry.timer(name(prefix, "move-requests"));
- this.otherRequests = metricRegistry.timer(name(prefix, "other-requests"));
-
- metricRegistry.register(name(prefix, "percent-4xx-1m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getOneMinuteRate(),
- requests.getOneMinuteRate());
- }
- });
-
- metricRegistry.register(name(prefix, "percent-4xx-5m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getFiveMinuteRate(),
- requests.getFiveMinuteRate());
- }
- });
-
- metricRegistry.register(name(prefix, "percent-4xx-15m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[3].getFifteenMinuteRate(),
- requests.getFifteenMinuteRate());
- }
- });
-
- metricRegistry.register(name(prefix, "percent-5xx-1m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getOneMinuteRate(),
- requests.getOneMinuteRate());
- }
- });
+ final String prefix = getMetricPrefix();
+
+ this.requests = metricRegistry.timer(name(prefix, NAME_REQUESTS));
+ this.dispatches = metricRegistry.timer(name(prefix, NAME_DISPATCHES));
+
+ this.activeRequests = metricRegistry.counter(name(prefix, NAME_ACTIVE_REQUESTS));
+ this.activeDispatches = metricRegistry.counter(name(prefix, NAME_ACTIVE_DISPATCHES));
+ this.activeSuspended = metricRegistry.counter(name(prefix, NAME_ACTIVE_SUSPENDED));
+
+ this.asyncDispatches = metricRegistry.meter(name(prefix, NAME_ASYNC_DISPATCHES));
+ this.asyncTimeouts = metricRegistry.meter(name(prefix, NAME_ASYNC_TIMEOUTS));
+
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(responseMeteredLevel) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+
+ this.getRequests = metricRegistry.timer(name(prefix, NAME_GET_REQUESTS));
+ this.postRequests = metricRegistry.timer(name(prefix, NAME_POST_REQUESTS));
+ this.headRequests = metricRegistry.timer(name(prefix, NAME_HEAD_REQUESTS));
+ this.putRequests = metricRegistry.timer(name(prefix, NAME_PUT_REQUESTS));
+ this.deleteRequests = metricRegistry.timer(name(prefix, NAME_DELETE_REQUESTS));
+ this.optionsRequests = metricRegistry.timer(name(prefix, NAME_OPTIONS_REQUESTS));
+ this.traceRequests = metricRegistry.timer(name(prefix, NAME_TRACE_REQUESTS));
+ this.connectRequests = metricRegistry.timer(name(prefix, NAME_CONNECT_REQUESTS));
+ this.moveRequests = metricRegistry.timer(name(prefix, NAME_MOVE_REQUESTS));
+ this.otherRequests = metricRegistry.timer(name(prefix, NAME_OTHER_REQUESTS));
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ this.responses = Collections.unmodifiableList(Arrays.asList(
+ metricRegistry.meter(name(prefix, NAME_1XX_RESPONSES)), // 1xx
+ metricRegistry.meter(name(prefix, NAME_2XX_RESPONSES)), // 2xx
+ metricRegistry.meter(name(prefix, NAME_3XX_RESPONSES)), // 3xx
+ metricRegistry.meter(name(prefix, NAME_4XX_RESPONSES)), // 4xx
+ metricRegistry.meter(name(prefix, NAME_5XX_RESPONSES)) // 5xx
+ ));
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
- metricRegistry.register(name(prefix, "percent-5xx-5m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getFiveMinuteRate(),
- requests.getFiveMinuteRate());
- }
- });
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
- metricRegistry.register(name(prefix, "percent-5xx-15m"), new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- return Ratio.of(responses[4].getFifteenMinuteRate(),
- requests.getFifteenMinuteRate());
- }
- });
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_15M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
- this.listener = new AsyncListener() {
- private long startTime;
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
- @Override
- public void onTimeout(AsyncEvent event) throws IOException {
- asyncTimeouts.mark();
- }
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_15M), new RatioGauge() {
+ @Override
+ public Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+ } else {
+ this.responses = Collections.emptyList();
+ }
- @Override
- public void onStartAsync(AsyncEvent event) throws IOException {
- startTime = System.currentTimeMillis();
- event.getAsyncContext().addListener(this);
- }
- @Override
- public void onError(AsyncEvent event) throws IOException {
- }
+ this.listener = new AsyncAttachingListener();
+ }
- @Override
- public void onComplete(AsyncEvent event) throws IOException {
- final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
- final HttpServletRequest request = (HttpServletRequest) state.getRequest();
- final HttpServletResponse response = (HttpServletResponse) state.getResponse();
- updateResponses(request, response, startTime, true);
- if (state.getHttpChannelState().getState() != HttpChannelState.State.DISPATCHED) {
- activeSuspended.dec();
- }
- }
- };
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_SUSPENDED));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_TIMEOUTS));
+ metricRegistry.remove(name(prefix, NAME_1XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_2XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_3XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_4XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_5XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_GET_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_POST_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_HEAD_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PUT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DELETE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OPTIONS_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_TRACE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_CONNECT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_MOVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OTHER_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_15M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_15M));
+
+ if (responseCodeMeters != null) {
+ responseCodeMeters.keySet().stream()
+ .map(sc -> name(getMetricPrefix(), String.format("%d-responses", sc)))
+ .forEach(metricRegistry::remove);
+ }
+ super.doStop();
}
@Override
@@ -232,7 +313,7 @@ public class InstrumentedHandler extends HandlerWrapper {
// resumed request
start = System.currentTimeMillis();
activeSuspended.dec();
- if (state.getState() == HttpChannelState.State.DISPATCHED) {
+ if (state.getState() == DISPATCHED_HACK) {
asyncDispatches.mark();
}
}
@@ -286,18 +367,86 @@ public class InstrumentedHandler extends HandlerWrapper {
}
private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start, boolean isHandled) {
- final int responseStatus;
if (isHandled) {
- responseStatus = response.getStatus() / 100;
+ mark(response.getStatus());
} else {
- responseStatus = 4; // will end up with a 404 response sent by HttpChannel.handle
- }
- if (responseStatus >= 1 && responseStatus <= 5) {
- responses[responseStatus - 1].mark();
+ mark(404);; // will end up with a 404 response sent by HttpChannel.handle
}
activeRequests.dec();
final long elapsedTime = System.currentTimeMillis() - start;
requests.update(elapsedTime, TimeUnit.MILLISECONDS);
requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
}
+
+ private void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(responseMeteredLevel)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ responses.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(getMetricPrefix(), String.format("%d-responses", sc))));
+ }
+
+ private String getMetricPrefix() {
+ return this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name);
+ }
+
+ private class AsyncAttachingListener implements AsyncListener {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ event.getAsyncContext().addListener(new InstrumentedAsyncListener());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {}
+ };
+
+ private class InstrumentedAsyncListener implements AsyncListener {
+ private final long startTime;
+
+ InstrumentedAsyncListener() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {
+ asyncTimeouts.mark();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {
+ final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
+ final HttpServletRequest request = (HttpServletRequest) state.getRequest();
+ final HttpServletResponse response = (HttpServletResponse) state.getResponse();
+ updateResponses(request, response, startTime, true);
+ if (!state.getHttpChannelState().isSuspended()) {
+ activeSuspended.dec();
+ }
+ }
+ }
}
diff --git a/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedHttpChannelListener.java b/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedHttpChannelListener.java
new file mode 100644
index 0000000..8b41f3e
--- /dev/null
+++ b/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedHttpChannelListener.java
@@ -0,0 +1,426 @@
+package com.codahale.metrics.jetty9;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.AsyncContextState;
+import org.eclipse.jetty.server.HttpChannel.Listener;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
+
+/**
+ * A Jetty {@link org.eclipse.jetty.server.HttpChannel.Listener} implementation which records various metrics about
+ * underlying channel instance. Unlike {@link InstrumentedHandler} that uses internal API, this class should be
+ * future proof. To install it, just add instance of this class to {@link org.eclipse.jetty.server.Connector} as bean.
+ *
+ * @since TBD
+ */
+public class InstrumentedHttpChannelListener
+ implements Listener
+{
+ private static final String START_ATTR = InstrumentedHttpChannelListener.class.getName() + ".start";
+ private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+
+ private final MetricRegistry metricRegistry;
+
+ // the requests handled by this handler, excluding active
+ private final Timer requests;
+
+ // the number of dispatches seen by this handler, excluding active
+ private final Timer dispatches;
+
+ // the number of active requests
+ private final Counter activeRequests;
+
+ // the number of active dispatches
+ private final Counter activeDispatches;
+
+ // the number of requests currently suspended.
+ private final Counter activeSuspended;
+
+ // the number of requests that have been asynchronously dispatched
+ private final Meter asyncDispatches;
+
+ // the number of requests that expired while suspended
+ private final Meter asyncTimeouts;
+
+ private final ResponseMeteredLevel responseMeteredLevel;
+ private final List<Meter> responses;
+ private final Map<Integer, Meter> responseCodeMeters;
+ private final String prefix;
+ private final Timer getRequests;
+ private final Timer postRequests;
+ private final Timer headRequests;
+ private final Timer putRequests;
+ private final Timer deleteRequests;
+ private final Timer optionsRequests;
+ private final Timer traceRequests;
+ private final Timer connectRequests;
+ private final Timer moveRequests;
+ private final Timer otherRequests;
+
+ private final AsyncListener listener;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry) {
+ this(registry, null, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param pref the prefix to use for the metrics names
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry, String pref) {
+ this(registry, pref, COARSE);
+ }
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ * @param pref the prefix to use for the metrics names
+ * @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
+ */
+ public InstrumentedHttpChannelListener(MetricRegistry registry, String pref, ResponseMeteredLevel responseMeteredLevel) {
+ this.metricRegistry = registry;
+
+ this.prefix = (pref == null) ? getClass().getName() : pref;
+
+ this.requests = metricRegistry.timer(name(prefix, "requests"));
+ this.dispatches = metricRegistry.timer(name(prefix, "dispatches"));
+
+ this.activeRequests = metricRegistry.counter(name(prefix, "active-requests"));
+ this.activeDispatches = metricRegistry.counter(name(prefix, "active-dispatches"));
+ this.activeSuspended = metricRegistry.counter(name(prefix, "active-suspended"));
+
+ this.asyncDispatches = metricRegistry.meter(name(prefix, "async-dispatches"));
+ this.asyncTimeouts = metricRegistry.meter(name(prefix, "async-timeouts"));
+
+ this.responseMeteredLevel = responseMeteredLevel;
+ this.responseCodeMeters = DETAILED_METER_LEVELS.contains(responseMeteredLevel) ? new ConcurrentHashMap<>() : Collections.emptyMap();
+ this.responses = COARSE_METER_LEVELS.contains(responseMeteredLevel) ?
+ Collections.unmodifiableList(Arrays.asList(
+ registry.meter(name(prefix, "1xx-responses")), // 1xx
+ registry.meter(name(prefix, "2xx-responses")), // 2xx
+ registry.meter(name(prefix, "3xx-responses")), // 3xx
+ registry.meter(name(prefix, "4xx-responses")), // 4xx
+ registry.meter(name(prefix, "5xx-responses")) // 5xx
+ )) : Collections.emptyList();
+
+ this.getRequests = metricRegistry.timer(name(prefix, "get-requests"));
+ this.postRequests = metricRegistry.timer(name(prefix, "post-requests"));
+ this.headRequests = metricRegistry.timer(name(prefix, "head-requests"));
+ this.putRequests = metricRegistry.timer(name(prefix, "put-requests"));
+ this.deleteRequests = metricRegistry.timer(name(prefix, "delete-requests"));
+ this.optionsRequests = metricRegistry.timer(name(prefix, "options-requests"));
+ this.traceRequests = metricRegistry.timer(name(prefix, "trace-requests"));
+ this.connectRequests = metricRegistry.timer(name(prefix, "connect-requests"));
+ this.moveRequests = metricRegistry.timer(name(prefix, "move-requests"));
+ this.otherRequests = metricRegistry.timer(name(prefix, "other-requests"));
+
+ metricRegistry.register(name(prefix, "percent-4xx-1m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-4xx-5m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-4xx-15m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(3).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-1m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-5m"), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, "percent-5xx-15m"), new RatioGauge() {
+ @Override
+ public Ratio getRatio() {
+ return Ratio.of(responses.get(4).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+
+ this.listener = new AsyncAttachingListener();
+ }
+
+ @Override
+ public void onRequestBegin(final Request request) {
+
+ }
+
+ @Override
+ public void onBeforeDispatch(final Request request) {
+ before(request);
+ }
+
+ @Override
+ public void onDispatchFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onAfterDispatch(final Request request) {
+ after(request);
+ }
+
+ @Override
+ public void onRequestContent(final Request request, final ByteBuffer content) {
+
+ }
+
+ @Override
+ public void onRequestContentEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestTrailers(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onRequestFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onResponseBegin(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseCommit(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseContent(final Request request, final ByteBuffer content) {
+
+ }
+
+ @Override
+ public void onResponseEnd(final Request request) {
+
+ }
+
+ @Override
+ public void onResponseFailure(final Request request, final Throwable failure) {
+
+ }
+
+ @Override
+ public void onComplete(final Request request) {
+
+ }
+
+ private void before(final Request request) {
+ activeDispatches.inc();
+
+ final long start;
+ final HttpChannelState state = request.getHttpChannelState();
+ if (state.isInitial()) {
+ // new request
+ activeRequests.inc();
+ start = request.getTimeStamp();
+ state.addListener(listener);
+ } else {
+ // resumed request
+ start = System.currentTimeMillis();
+ activeSuspended.dec();
+ if (state.isAsyncStarted()) {
+ asyncDispatches.mark();
+ }
+ }
+ request.setAttribute(START_ATTR, start);
+ }
+
+ private void after(final Request request) {
+ final long start = (long) request.getAttribute(START_ATTR);
+ final long now = System.currentTimeMillis();
+ final long dispatched = now - start;
+
+ activeDispatches.dec();
+ dispatches.update(dispatched, TimeUnit.MILLISECONDS);
+
+ final HttpChannelState state = request.getHttpChannelState();
+ if (state.isSuspended()) {
+ activeSuspended.inc();
+ } else if (state.isInitial()) {
+ updateResponses(request, request.getResponse(), start, request.isHandled());
+ }
+ // else onCompletion will handle it.
+ }
+
+ private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start, boolean isHandled) {
+ if (isHandled) {
+ mark(response.getStatus());
+ } else {
+ mark(404); // will end up with a 404 response sent by HttpChannel.handle
+ }
+ activeRequests.dec();
+ final long elapsedTime = System.currentTimeMillis() - start;
+ requests.update(elapsedTime, TimeUnit.MILLISECONDS);
+ requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
+ }
+
+ private void mark(int statusCode) {
+ if (DETAILED_METER_LEVELS.contains(responseMeteredLevel)) {
+ getResponseCodeMeter(statusCode).mark();
+ }
+
+ if (COARSE_METER_LEVELS.contains(responseMeteredLevel)) {
+ final int responseStatus = statusCode / 100;
+ if (responseStatus >= 1 && responseStatus <= 5) {
+ responses.get(responseStatus - 1).mark();
+ }
+ }
+ }
+
+ private Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(prefix, String.format("%d-responses", sc))));
+ }
+
+ private Timer requestTimer(String method) {
+ final HttpMethod m = HttpMethod.fromString(method);
+ if (m == null) {
+ return otherRequests;
+ } else {
+ switch (m) {
+ case GET:
+ return getRequests;
+ case POST:
+ return postRequests;
+ case PUT:
+ return putRequests;
+ case HEAD:
+ return headRequests;
+ case DELETE:
+ return deleteRequests;
+ case OPTIONS:
+ return optionsRequests;
+ case TRACE:
+ return traceRequests;
+ case CONNECT:
+ return connectRequests;
+ case MOVE:
+ return moveRequests;
+ default:
+ return otherRequests;
+ }
+ }
+ }
+
+ private class AsyncAttachingListener implements AsyncListener {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ event.getAsyncContext().addListener(new InstrumentedAsyncListener());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {}
+ };
+
+ private class InstrumentedAsyncListener implements AsyncListener {
+ private final long startTime;
+
+ InstrumentedAsyncListener() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {
+ asyncTimeouts.mark();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {
+ final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
+ final HttpServletRequest request = (HttpServletRequest) state.getRequest();
+ final HttpServletResponse response = (HttpServletResponse) state.getResponse();
+ updateResponses(request, response, startTime, true);
+ if (!state.getHttpChannelState().isSuspended()) {
+ activeSuspended.dec();
+ }
+ }
+ }
+}
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 ca79b60..d3889ca 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
@@ -1,6 +1,5 @@
package com.codahale.metrics.jetty9;
-import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.RatioGauge;
import org.eclipse.jetty.util.annotation.Name;
@@ -11,6 +10,12 @@ import java.util.concurrent.BlockingQueue;
import static com.codahale.metrics.MetricRegistry.name;
public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
+ private static final String NAME_UTILIZATION = "utilization";
+ private static final String NAME_UTILIZATION_MAX = "utilization-max";
+ private static final String NAME_SIZE = "size";
+ private static final String NAME_JOBS = "jobs";
+ private static final String NAME_JOBS_QUEUE_UTILIZATION = "jobs-queue-utilization";
+
private final MetricRegistry metricRegistry;
private String prefix;
@@ -67,33 +72,47 @@ public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
protected void doStart() throws Exception {
super.doStart();
- final String prefix = this.prefix == null ? name(QueuedThreadPool.class, getName()) : name(this.prefix, getName());
+ final String prefix = getMetricPrefix();
- metricRegistry.register(name(prefix, "utilization"), new RatioGauge() {
+ metricRegistry.register(name(prefix, NAME_UTILIZATION), new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(getThreads() - getIdleThreads(), getThreads());
}
});
- metricRegistry.register(name(prefix, "utilization-max"), new RatioGauge() {
+ metricRegistry.register(name(prefix, NAME_UTILIZATION_MAX), new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(getThreads() - getIdleThreads(), getMaxThreads());
}
});
- metricRegistry.register(name(prefix, "size"), new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- return getThreads();
- }
- });
- metricRegistry.register(name(prefix, "jobs"), new Gauge<Integer>() {
+ metricRegistry.registerGauge(name(prefix, NAME_SIZE), this::getThreads);
+ // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
+ // ArrayBlockingQueue for its queue, and is therefore a constant-time operation.
+ metricRegistry.registerGauge(name(prefix, NAME_JOBS), () -> getQueue().size());
+ metricRegistry.register(name(prefix, NAME_JOBS_QUEUE_UTILIZATION), new RatioGauge() {
@Override
- public Integer getValue() {
- // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
- // ArrayBlockingQueue for its queue, and is therefore a constant-time operation.
- return getQueue().size();
+ protected Ratio getRatio() {
+ BlockingQueue<Runnable> queue = getQueue();
+ return Ratio.of(queue.size(), queue.size() + queue.remainingCapacity());
}
});
}
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION));
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION_MAX));
+ metricRegistry.remove(name(prefix, NAME_SIZE));
+ metricRegistry.remove(name(prefix, NAME_JOBS));
+ metricRegistry.remove(name(prefix, NAME_JOBS_QUEUE_UTILIZATION));
+
+ super.doStop();
+ }
+
+ private String getMetricPrefix() {
+ return this.prefix == null ? name(QueuedThreadPool.class, getName()) : name(this.prefix, getName());
+ }
}
diff --git a/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactoryTest.java b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactoryTest.java
index 5d825dd..d06535d 100644
--- a/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactoryTest.java
+++ b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactoryTest.java
@@ -1,5 +1,6 @@
package com.codahale.metrics.jetty9;
+import com.codahale.metrics.Counter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import org.eclipse.jetty.client.HttpClient;
@@ -19,7 +20,6 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
-import static com.codahale.metrics.MetricRegistry.name;
import static org.assertj.core.api.Assertions.assertThat;
public class InstrumentedConnectionFactoryTest {
@@ -27,7 +27,8 @@ public class InstrumentedConnectionFactoryTest {
private final Server server = new Server();
private final ServerConnector connector =
new ServerConnector(server, new InstrumentedConnectionFactory(new HttpConnectionFactory(),
- registry.timer("http.connections")));
+ registry.timer("http.connections"),
+ registry.counter("http.active-connections")));
private final HttpClient client = new HttpClient();
@Before
@@ -66,8 +67,27 @@ public class InstrumentedConnectionFactoryTest {
Thread.sleep(100); // make sure the connection is closed
- final Timer timer = registry.timer(name("http.connections"));
+ final Timer timer = registry.timer(MetricRegistry.name("http.connections"));
assertThat(timer.getCount())
.isEqualTo(1);
}
+
+ @Test
+ public void instrumentsActiveConnections() throws Exception {
+ final Counter counter = registry.counter("http.active-connections");
+
+ final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertThat(counter.getCount())
+ .isEqualTo(1);
+
+ client.stop(); // close the connection
+
+ Thread.sleep(100); // make sure the connection is closed
+
+ assertThat(counter.getCount())
+ .isEqualTo(0);
+ }
}
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 407bbf0..12dd977 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
@@ -9,6 +9,7 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import javax.servlet.AsyncContext;
@@ -17,19 +18,22 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.LockSupport;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
public class InstrumentedHandlerTest {
private final HttpClient client = new HttpClient();
private final MetricRegistry registry = new MetricRegistry();
private final Server server = new Server();
private final ServerConnector connector = new ServerConnector(server);
- private final InstrumentedHandler handler = new InstrumentedHandler(registry);
+ private final InstrumentedHandler handler = new InstrumentedHandler(registry, null, ALL);
@Before
public void setUp() throws Exception {
@@ -50,52 +54,54 @@ public class InstrumentedHandlerTest {
@Test
public void hasAName() throws Exception {
assertThat(handler.getName())
- .isEqualTo("handler");
+ .isEqualTo("handler");
}
@Test
- public void createsMetricsForTheHandler() throws Exception {
+ public void createsAndRemovesMetricsForTheHandler() throws Exception {
final ContentResponse response = client.GET(uri("/hello"));
assertThat(response.getStatus())
- .isEqualTo(404);
+ .isEqualTo(404);
assertThat(registry.getNames())
- .containsOnly(
- 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")
- );
-
- assertThat(registry.getMeters().get(metricName() + ".4xx-responses")
- .getCount()).isGreaterThan(0L);
- }
+ .containsOnly(
+ 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.404-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")
+ );
+ server.stop();
+
+ assertThat(registry.getNames())
+ .isEmpty();
+ }
@Test
public void responseTimesAreRecordedForBlockingResponses() throws Exception {
@@ -103,32 +109,61 @@ public class InstrumentedHandlerTest {
final ContentResponse response = client.GET(uri("/blocking"));
assertThat(response.getStatus())
- .isEqualTo(200);
+ .isEqualTo(200);
assertResponseTimesValid();
}
@Test
+ public void doStopDoesNotThrowNPE() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, null, ALL);
+ handler.setHandler(new TestHandler());
+
+ assertThatCode(handler::doStop).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void gaugesAreRegisteredWithResponseMeteredLevelCoarse() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, "coarse", COARSE);
+ handler.setHandler(new TestHandler());
+ handler.setName("handler");
+ handler.doStart();
+ assertThat(registry.getGauges()).containsKey("coarse.handler.percent-4xx-1m");
+ }
+
+ @Test
+ public void gaugesAreNotRegisteredWithResponseMeteredLevelDetailed() throws Exception {
+ InstrumentedHandler handler = new InstrumentedHandler(registry, "detailed", DETAILED);
+ handler.setHandler(new TestHandler());
+ handler.setName("handler");
+ handler.doStart();
+ assertThat(registry.getGauges()).doesNotContainKey("coarse.handler.percent-4xx-1m");
+ }
+
+ @Test
+ @Ignore("flaky on virtual machines")
public void responseTimesAreRecordedForAsyncResponses() throws Exception {
final ContentResponse response = client.GET(uri("/async"));
assertThat(response.getStatus())
- .isEqualTo(200);
+ .isEqualTo(200);
assertResponseTimesValid();
}
private void assertResponseTimesValid() {
assertThat(registry.getMeters().get(metricName() + ".2xx-responses")
+ .getCount()).isGreaterThan(0L);
+ assertThat(registry.getMeters().get(metricName() + ".200-responses")
.getCount()).isGreaterThan(0L);
assertThat(registry.getTimers().get(metricName() + ".get-requests")
- .getSnapshot().getMedian()).isGreaterThan(0.0).isLessThan(TimeUnit.SECONDS.toNanos(1));
+ .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));
+ .getSnapshot().getMedian()).isGreaterThan(0.0).isLessThan(TimeUnit.SECONDS.toNanos(1));
}
private String uri(String path) {
@@ -141,59 +176,71 @@ public class InstrumentedHandlerTest {
/**
* test handler.
- *
+ * <p>
* Supports
- *
+ * <p>
* /blocking - uses the standard servlet api
* /async - uses the 3.1 async api to complete the request
- *
+ * <p>
* 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
+ String path,
+ Request request,
+ final HttpServletRequest httpServletRequest,
+ final HttpServletResponse httpServletResponse
) throws IOException, ServletException {
- if (path.equals("/blocking")) {
- request.setHandled(true);
- 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")) {
- request.setHandled(true);
- final AsyncContext context = request.startAsync();
- Thread t = new Thread() {
- public void run() {
- LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
+ switch (path) {
+ case "/blocking":
+ request.setHandled(true);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ httpServletResponse.getWriter().write("some content from the blocking request\n");
+ break;
+ case "/async":
+ request.setHandled(true);
+ final AsyncContext context = request.startAsync();
+ Thread t = new Thread(() -> {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
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();
- }
+ new WriteListener() {
+ @Override
+ public void onWritePossible() throws IOException {
+ servletOutputStream.write("some content from the async\n"
+ .getBytes(StandardCharsets.UTF_8));
+ context.complete();
}
+
+ @Override
+ public void onError(Throwable throwable) {
+ context.complete();
+ }
+ }
);
} catch (IOException e) {
context.complete();
}
- }
- };
- t.start();
+ });
+ t.start();
+ break;
+ default:
+ break;
}
}
}
diff --git a/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedHttpChannelListenerTest.java b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedHttpChannelListenerTest.java
new file mode 100644
index 0000000..2bddebe
--- /dev/null
+++ b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedHttpChannelListenerTest.java
@@ -0,0 +1,212 @@
+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.AbstractHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedHttpChannelListenerTest {
+ private final HttpClient client = new HttpClient();
+ private final Server server = new Server();
+ private final ServerConnector connector = new ServerConnector(server);
+ private final TestHandler handler = new TestHandler();
+ private MetricRegistry registry;
+
+ @Before
+ public void setUp() throws Exception {
+ registry = new MetricRegistry();
+ connector.addBean(new InstrumentedHttpChannelListener(registry, MetricRegistry.name(TestHandler.class, "handler"), ALL));
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void createsMetricsForTheHandler() throws Exception {
+ final ContentResponse response = client.GET(uri("/hello"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(404);
+
+ assertThat(registry.getNames())
+ .containsOnly(
+ metricName("1xx-responses"),
+ metricName("2xx-responses"),
+ metricName("3xx-responses"),
+ metricName("404-responses"),
+ metricName("4xx-responses"),
+ metricName("5xx-responses"),
+ metricName("percent-4xx-1m"),
+ metricName("percent-4xx-5m"),
+ metricName("percent-4xx-15m"),
+ metricName("percent-5xx-1m"),
+ metricName("percent-5xx-5m"),
+ metricName("percent-5xx-15m"),
+ metricName("requests"),
+ metricName("active-suspended"),
+ metricName("async-dispatches"),
+ metricName("async-timeouts"),
+ metricName("get-requests"),
+ metricName("put-requests"),
+ metricName("active-dispatches"),
+ metricName("trace-requests"),
+ metricName("other-requests"),
+ metricName("connect-requests"),
+ metricName("dispatches"),
+ metricName("head-requests"),
+ metricName("post-requests"),
+ metricName("options-requests"),
+ metricName("active-requests"),
+ metricName("delete-requests"),
+ metricName("move-requests")
+ );
+ }
+
+
+ @Test
+ public void responseTimesAreRecordedForBlockingResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/blocking"));
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.getMediaType()).isEqualTo("text/plain");
+ assertThat(response.getContentAsString()).isEqualTo("some content from the blocking request");
+
+ assertResponseTimesValid();
+ }
+
+ @Test
+ public void responseTimesAreRecordedForAsyncResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/async"));
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.getMediaType()).isEqualTo("text/plain");
+ assertThat(response.getContentAsString()).isEqualTo("some content from the async");
+
+ assertResponseTimesValid();
+ }
+
+ private void assertResponseTimesValid() {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ assertThat(registry.getMeters().get(metricName("2xx-responses"))
+ .getCount()).isPositive();
+ assertThat(registry.getMeters().get(metricName("200-responses"))
+ .getCount()).isPositive();
+
+ assertThat(registry.getTimers().get(metricName("get-requests"))
+ .getSnapshot().getMedian()).isPositive();
+
+ assertThat(registry.getTimers().get(metricName("requests"))
+ .getSnapshot().getMedian()).isPositive();
+ }
+
+ private String uri(String path) {
+ return "http://localhost:" + connector.getLocalPort() + path;
+ }
+
+ private String metricName(String metricName) {
+ return MetricRegistry.name(TestHandler.class.getName(), "handler", metricName);
+ }
+
+ /**
+ * test handler.
+ * <p>
+ * Supports
+ * <p>
+ * /blocking - uses the standard servlet api
+ * /async - uses the 3.1 async api to complete the request
+ * <p>
+ * 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 {
+ switch (path) {
+ case "/blocking":
+ request.setHandled(true);
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ httpServletResponse.getWriter().write("some content from the blocking request");
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ httpServletResponse.setStatus(500);
+ Thread.currentThread().interrupt();
+ }
+ break;
+ case "/async":
+ request.setHandled(true);
+ final AsyncContext context = request.startAsync();
+ Thread t = new Thread(() -> {
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ httpServletResponse.setStatus(500);
+ Thread.currentThread().interrupt();
+ }
+ final ServletOutputStream servletOutputStream;
+ try {
+ servletOutputStream = httpServletResponse.getOutputStream();
+ servletOutputStream.setWriteListener(
+ new WriteListener() {
+ @Override
+ public void onWritePossible() throws IOException {
+ servletOutputStream.write("some content from the async"
+ .getBytes(StandardCharsets.UTF_8));
+ context.complete();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ context.complete();
+ }
+ }
+ );
+ } catch (IOException e) {
+ context.complete();
+ }
+ });
+ t.start();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
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
index 2b4ed04..2b4ddcc 100644
--- a/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPoolTest.java
+++ b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPoolTest.java
@@ -1,49 +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.Before;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
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);
+ private MetricRegistry metricRegistry;
+ private InstrumentedQueuedThreadPool iqtp;
- @After
- public void tearDown() throws Exception {
- iqtp.stop();
+ @Before
+ public void setUp() {
+ metricRegistry = new MetricRegistry();
+ iqtp = new InstrumentedQueuedThreadPool(metricRegistry);
}
@Test
- public void customMetricsPrefix() throws Exception{
+ public void customMetricsPrefix() throws Exception {
iqtp.setPrefix(PREFIX);
- iqtp.doStart();
+ iqtp.start();
- verify(metricRegistry, atLeastOnce()).register(metricNameCaptor.capture(), any(Metric.class));
- String metricName = metricNameCaptor.getValue();
- assertThat("Custom metric's prefix doesn't match", metricName, startsWith(PREFIX));
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("Custom metrics prefix doesn't match")
+ .allSatisfy(name -> assertThat(name).startsWith(PREFIX));
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
}
@Test
- public void metricsPrefixBackwardCompatible() throws Exception{
- iqtp.doStart();
+ public void metricsPrefixBackwardCompatible() throws Exception {
+ iqtp.start();
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .allSatisfy(name -> assertThat(name).startsWith(QueuedThreadPool.class.getName()));
- 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()));
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
}
-
}
diff --git a/metrics-jmx/pom.xml b/metrics-jmx/pom.xml
new file mode 100644
index 0000000..efab0ce
--- /dev/null
+++ b/metrics-jmx/pom.xml
@@ -0,0 +1,73 @@
+<?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">
+ <parent>
+ <artifactId>metrics-parent</artifactId>
+ <groupId>io.dropwizard.metrics</groupId>
+ <version>4.2.25</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>metrics-jmx</artifactId>
+ <name>Metrics Integration with JMX</name>
+ <packaging>bundle</packaging>
+ <description>
+ A set of classes which allow you to report metrics via JMX.
+ </description>
+
+ <properties>
+ <javaModuleName>com.codahale.metrics.jmx</javaModuleName>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-jmx/src/main/java/com/codahale/metrics/jmx/DefaultObjectNameFactory.java b/metrics-jmx/src/main/java/com/codahale/metrics/jmx/DefaultObjectNameFactory.java
new file mode 100644
index 0000000..c8ad1c2
--- /dev/null
+++ b/metrics-jmx/src/main/java/com/codahale/metrics/jmx/DefaultObjectNameFactory.java
@@ -0,0 +1,69 @@
+package com.codahale.metrics.jmx;
+
+import java.util.Hashtable;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultObjectNameFactory implements ObjectNameFactory {
+
+ private static final char[] QUOTABLE_CHARS = new char[] {',', '=', ':', '"'};
+ private static final Logger LOGGER = LoggerFactory.getLogger(JmxReporter.class);
+
+ @Override
+ public ObjectName createName(String type, String domain, String name) {
+ try {
+ ObjectName objectName;
+ Hashtable<String, String> properties = new Hashtable<>();
+
+ properties.put("name", name);
+ properties.put("type", type);
+ objectName = new ObjectName(domain, properties);
+
+ /*
+ * The only way we can find out if we need to quote the properties is by
+ * checking an ObjectName that we've constructed.
+ */
+ if (objectName.isDomainPattern()) {
+ domain = ObjectName.quote(domain);
+ }
+ if (objectName.isPropertyValuePattern("name") || shouldQuote(objectName.getKeyProperty("name"))) {
+ properties.put("name", ObjectName.quote(name));
+ }
+ if (objectName.isPropertyValuePattern("type") || shouldQuote(objectName.getKeyProperty("type"))) {
+ properties.put("type", ObjectName.quote(type));
+ }
+ objectName = new ObjectName(domain, properties);
+
+ return objectName;
+ } catch (MalformedObjectNameException e) {
+ try {
+ return new ObjectName(domain, "name", ObjectName.quote(name));
+ } catch (MalformedObjectNameException e1) {
+ LOGGER.warn("Unable to register {} {}", type, name, e1);
+ throw new RuntimeException(e1);
+ }
+ }
+ }
+
+ /**
+ * Determines whether the value requires quoting.
+ * According to the {@link ObjectName} documentation, values can be quoted or unquoted. Unquoted
+ * values may not contain any of the characters comma, equals, colon, or quote.
+ *
+ * @param value a value to test
+ * @return true when it requires quoting, false otherwise
+ */
+ private boolean shouldQuote(final String value) {
+ for (char quotableChar : QUOTABLE_CHARS) {
+ if (value.indexOf(quotableChar) != -1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/JmxReporter.java b/metrics-jmx/src/main/java/com/codahale/metrics/jmx/JmxReporter.java
similarity index 93%
rename from metrics-core/src/main/java/com/codahale/metrics/JmxReporter.java
rename to metrics-jmx/src/main/java/com/codahale/metrics/jmx/JmxReporter.java
index 2be42f4..8f76071 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/JmxReporter.java
+++ b/metrics-jmx/src/main/java/com/codahale/metrics/jmx/JmxReporter.java
@@ -1,9 +1,25 @@
-package com.codahale.metrics;
-
+package com.codahale.metrics.jmx;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metered;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.MetricRegistryListener;
+import com.codahale.metrics.Reporter;
+import com.codahale.metrics.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.management.*;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.JMException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
import java.io.Closeable;
import java.lang.management.ManagementFactory;
import java.util.Collections;
@@ -54,7 +70,7 @@ public class JmxReporter implements Reporter, Closeable {
/**
* Register MBeans with the given {@link MBeanServer}.
*
- * @param mBeanServer an {@link MBeanServer}
+ * @param mBeanServer an {@link MBeanServer}
* @return {@code this}
*/
public Builder registerWith(MBeanServer mBeanServer) {
@@ -74,13 +90,13 @@ public class JmxReporter implements Reporter, Closeable {
}
public Builder createsObjectNamesWith(ObjectNameFactory onFactory) {
- if(onFactory == null) {
- throw new IllegalArgumentException("null objectNameFactory");
- }
- this.objectNameFactory = onFactory;
- return this;
+ if (onFactory == null) {
+ throw new IllegalArgumentException("null objectNameFactory");
+ }
+ this.objectNameFactory = onFactory;
+ return this;
}
-
+
/**
* Convert durations to the given time unit.
*
@@ -138,8 +154,8 @@ public class JmxReporter implements Reporter, Closeable {
*/
public JmxReporter build() {
final MetricTimeUnits timeUnits = new MetricTimeUnits(rateUnit, durationUnit, specificRateUnits, specificDurationUnits);
- if (mBeanServer==null) {
- mBeanServer = ManagementFactory.getPlatformMBeanServer();
+ if (mBeanServer == null) {
+ mBeanServer = ManagementFactory.getPlatformMBeanServer();
}
return new JmxReporter(mBeanServer, domain, registry, filter, timeUnits, objectNameFactory);
}
@@ -147,13 +163,10 @@ public class JmxReporter implements Reporter, Closeable {
private static final Logger LOGGER = LoggerFactory.getLogger(JmxReporter.class);
- // CHECKSTYLE:OFF
@SuppressWarnings("UnusedDeclaration")
public interface MetricMBean {
ObjectName objectName();
}
- // CHECKSTYLE:ON
-
private abstract static class AbstractBean implements MetricMBean {
private final ObjectName objectName;
@@ -168,12 +181,11 @@ public class JmxReporter implements Reporter, Closeable {
}
}
- // CHECKSTYLE:OFF
@SuppressWarnings("UnusedDeclaration")
public interface JmxGaugeMBean extends MetricMBean {
Object getValue();
+ Number getNumber();
}
- // CHECKSTYLE:ON
private static class JmxGauge extends AbstractBean implements JmxGaugeMBean {
private final Gauge<?> metric;
@@ -187,14 +199,18 @@ public class JmxReporter implements Reporter, Closeable {
public Object getValue() {
return metric.getValue();
}
+
+ @Override
+ public Number getNumber() {
+ Object value = metric.getValue();
+ return value instanceof Number ? (Number) value : 0;
+ }
}
- // CHECKSTYLE:OFF
@SuppressWarnings("UnusedDeclaration")
public interface JmxCounterMBean extends MetricMBean {
long getCount();
}
- // CHECKSTYLE:ON
private static class JmxCounter extends AbstractBean implements JmxCounterMBean {
private final Counter metric;
@@ -210,7 +226,6 @@ public class JmxReporter implements Reporter, Closeable {
}
}
- // CHECKSTYLE:OFF
@SuppressWarnings("UnusedDeclaration")
public interface JmxHistogramMBean extends MetricMBean {
long getCount();
@@ -239,7 +254,6 @@ public class JmxReporter implements Reporter, Closeable {
long getSnapshotSize();
}
- // CHECKSTYLE:ON
private static class JmxHistogram implements JmxHistogramMBean {
private final ObjectName objectName;
@@ -315,12 +329,12 @@ public class JmxReporter implements Reporter, Closeable {
return metric.getSnapshot().getValues();
}
+ @Override
public long getSnapshotSize() {
return metric.getSnapshot().size();
}
}
- //CHECKSTYLE:OFF
@SuppressWarnings("UnusedDeclaration")
public interface JmxMeterMBean extends MetricMBean {
long getCount();
@@ -335,7 +349,6 @@ public class JmxReporter implements Reporter, Closeable {
String getRateUnit();
}
- //CHECKSTYLE:ON
private static class JmxMeter extends AbstractBean implements JmxMeterMBean {
private final Metered metric;
@@ -385,7 +398,6 @@ public class JmxReporter implements Reporter, Closeable {
}
}
- // CHECKSTYLE:OFF
@SuppressWarnings("UnusedDeclaration")
public interface JmxTimerMBean extends JmxMeterMBean {
double getMin();
@@ -409,9 +421,9 @@ public class JmxReporter implements Reporter, Closeable {
double get999thPercentile();
long[] values();
+
String getDurationUnit();
}
- // CHECKSTYLE:ON
static class JmxTimer extends JmxMeter implements JmxTimerMBean {
private final Timer metric;
@@ -502,7 +514,7 @@ public class JmxReporter implements Reporter, Closeable {
this.name = name;
this.filter = filter;
this.timeUnits = timeUnits;
- this.registered = new ConcurrentHashMap<ObjectName, ObjectName>();
+ this.registered = new ConcurrentHashMap<>();
this.objectNameFactory = objectNameFactory;
}
@@ -692,11 +704,11 @@ public class JmxReporter implements Reporter, Closeable {
}
public TimeUnit durationFor(String name) {
- return durationOverrides.containsKey(name) ? durationOverrides.get(name) : defaultDuration;
+ return durationOverrides.getOrDefault(name, defaultDuration);
}
public TimeUnit rateFor(String name) {
- return rateOverrides.containsKey(name) ? rateOverrides.get(name) : defaultRate;
+ return rateOverrides.getOrDefault(name, defaultRate);
}
}
@@ -707,7 +719,7 @@ public class JmxReporter implements Reporter, Closeable {
String domain,
MetricRegistry registry,
MetricFilter filter,
- MetricTimeUnits timeUnits,
+ MetricTimeUnits timeUnits,
ObjectNameFactory objectNameFactory) {
this.registry = registry;
this.listener = new JmxListener(mBeanServer, domain, filter, timeUnits, objectNameFactory);
diff --git a/metrics-jmx/src/main/java/com/codahale/metrics/jmx/ObjectNameFactory.java b/metrics-jmx/src/main/java/com/codahale/metrics/jmx/ObjectNameFactory.java
new file mode 100644
index 0000000..72400b0
--- /dev/null
+++ b/metrics-jmx/src/main/java/com/codahale/metrics/jmx/ObjectNameFactory.java
@@ -0,0 +1,8 @@
+package com.codahale.metrics.jmx;
+
+import javax.management.ObjectName;
+
+public interface ObjectNameFactory {
+
+ ObjectName createName(String type, String domain, String name);
+}
diff --git a/metrics-jmx/src/test/java/com/codahale/metrics/jmx/DefaultObjectNameFactoryTest.java b/metrics-jmx/src/test/java/com/codahale/metrics/jmx/DefaultObjectNameFactoryTest.java
new file mode 100644
index 0000000..590ad74
--- /dev/null
+++ b/metrics-jmx/src/test/java/com/codahale/metrics/jmx/DefaultObjectNameFactoryTest.java
@@ -0,0 +1,33 @@
+package com.codahale.metrics.jmx;
+
+import org.junit.Test;
+
+import javax.management.ObjectName;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+public class DefaultObjectNameFactoryTest {
+
+ @Test
+ public void createsObjectNameWithDomainInInput() {
+ DefaultObjectNameFactory f = new DefaultObjectNameFactory();
+ ObjectName on = f.createName("type", "com.domain", "something.with.dots");
+ assertThat(on.getDomain()).isEqualTo("com.domain");
+ }
+
+ @Test
+ public void createsObjectNameWithNameAsKeyPropertyName() {
+ DefaultObjectNameFactory f = new DefaultObjectNameFactory();
+ ObjectName on = f.createName("type", "com.domain", "something.with.dots");
+ assertThat(on.getKeyProperty("name")).isEqualTo("something.with.dots");
+ }
+
+ @Test
+ public void createsObjectNameWithNameWithDisallowedUnquotedCharacters() {
+ DefaultObjectNameFactory f = new DefaultObjectNameFactory();
+ ObjectName on = f.createName("type", "com.domain", "something.with.quotes(\"ABcd\")");
+ assertThatCode(() -> new ObjectName(on.toString())).doesNotThrowAnyException();
+ assertThat(on.getKeyProperty("name")).isEqualTo("\"something.with.quotes(\\\"ABcd\\\")\"");
+ }
+}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/JmxReporterTest.java b/metrics-jmx/src/test/java/com/codahale/metrics/jmx/JmxReporterTest.java
similarity index 57%
rename from metrics-core/src/test/java/com/codahale/metrics/JmxReporterTest.java
rename to metrics-jmx/src/test/java/com/codahale/metrics/jmx/JmxReporterTest.java
index 2ae96af..7c1dfa2 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/JmxReporterTest.java
+++ b/metrics-jmx/src/test/java/com/codahale/metrics/jmx/JmxReporterTest.java
@@ -1,31 +1,52 @@
-package com.codahale.metrics;
-
+package com.codahale.metrics.jmx;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import javax.management.*;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.InstanceNotFoundException;
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
-import static org.assertj.core.api.Assertions.*;
-import static org.mockito.Mockito.*;
+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.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+@SuppressWarnings("rawtypes")
public class JmxReporterTest {
private final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
private final String name = UUID.randomUUID().toString().replaceAll("[{\\-}]", "");
private final MetricRegistry registry = new MetricRegistry();
private final JmxReporter reporter = JmxReporter.forRegistry(registry)
- .registerWith(mBeanServer)
- .inDomain(name)
- .convertDurationsTo(TimeUnit.MILLISECONDS)
- .convertRatesTo(TimeUnit.SECONDS)
- .filter(MetricFilter.ALL)
- .build();
+ .registerWith(mBeanServer)
+ .inDomain(name)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .convertRatesTo(TimeUnit.SECONDS)
+ .filter(MetricFilter.ALL)
+ .build();
private final Gauge gauge = mock(Gauge.class);
private final Counter counter = mock(Counter.class);
@@ -95,47 +116,44 @@ public class JmxReporterTest {
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
reporter.stop();
}
@Test
public void registersMBeansForMetricObjectsUsingProvidedObjectNameFactory() throws Exception {
- ObjectName n = new ObjectName(name + ":name=dummy");
- try {
- String widgetName = "something";
- when(mockObjectNameFactory.createName(any(String.class), any(String.class), any(String.class))).thenReturn(n);
- Gauge aGauge = mock(Gauge.class);
- when(aGauge.getValue()).thenReturn(1);
-
- JmxReporter reporter = JmxReporter.forRegistry(registry)
- .registerWith(mBeanServer)
- .inDomain(name)
- .createsObjectNamesWith(mockObjectNameFactory)
- .build();
- registry.register(widgetName, aGauge);
- reporter.start();
- verify(mockObjectNameFactory).createName(eq("gauges"), any(String.class), eq("something"));
- //verifyNoMoreInteractions(mockObjectNameFactory);
- } finally {
- reporter.stop();
- if(mBeanServer.isRegistered(n)) {
- mBeanServer.unregisterMBean(n);
- }
- }
+ ObjectName n = new ObjectName(name + ":name=dummy");
+ try {
+ String widgetName = "something";
+ when(mockObjectNameFactory.createName(any(String.class), any(String.class), any(String.class))).thenReturn(n);
+ JmxReporter reporter = JmxReporter.forRegistry(registry)
+ .registerWith(mBeanServer)
+ .inDomain(name)
+ .createsObjectNamesWith(mockObjectNameFactory)
+ .build();
+ registry.registerGauge(widgetName, () -> 1);
+ reporter.start();
+ verify(mockObjectNameFactory).createName(eq("gauges"), any(String.class), eq("something"));
+ //verifyNoMoreInteractions(mockObjectNameFactory);
+ } finally {
+ reporter.stop();
+ if (mBeanServer.isRegistered(n)) {
+ mBeanServer.unregisterMBean(n);
+ }
+ }
}
-
+
@Test
public void registersMBeansForGauges() throws Exception {
- final AttributeList attributes = getAttributes("gauge", "Value");
+ final AttributeList attributes = getAttributes("gauges", "gauge", "Value", "Number");
assertThat(values(attributes))
- .contains(entry("Value", 1));
+ .contains(entry("Value", 1), entry("Number", 1));
}
@Test
public void registersMBeansForCounters() throws Exception {
- final AttributeList attributes = getAttributes("test.counter", "Count");
+ final AttributeList attributes = getAttributes("counters", "test.counter", "Count");
assertThat(values(attributes))
.contains(entry("Count", 100L));
@@ -143,19 +161,19 @@ public class JmxReporterTest {
@Test
public void registersMBeansForHistograms() throws Exception {
- final AttributeList attributes = getAttributes("test.histogram",
- "Count",
- "Max",
- "Mean",
- "Min",
- "StdDev",
- "50thPercentile",
- "75thPercentile",
- "95thPercentile",
- "98thPercentile",
- "99thPercentile",
- "999thPercentile",
- "SnapshotSize");
+ final AttributeList attributes = getAttributes("histograms", "test.histogram",
+ "Count",
+ "Max",
+ "Mean",
+ "Min",
+ "StdDev",
+ "50thPercentile",
+ "75thPercentile",
+ "95thPercentile",
+ "98thPercentile",
+ "99thPercentile",
+ "999thPercentile",
+ "SnapshotSize");
assertThat(values(attributes))
.contains(entry("Count", 1L))
@@ -169,19 +187,18 @@ public class JmxReporterTest {
.contains(entry("98thPercentile", 9.0))
.contains(entry("99thPercentile", 10.0))
.contains(entry("999thPercentile", 11.0))
- .contains(entry("SnapshotSize", 1L))
- ;
+ .contains(entry("SnapshotSize", 1L));
}
@Test
public void registersMBeansForMeters() throws Exception {
- final AttributeList attributes = getAttributes("test.meter",
- "Count",
- "MeanRate",
- "OneMinuteRate",
- "FiveMinuteRate",
- "FifteenMinuteRate",
- "RateUnit");
+ final AttributeList attributes = getAttributes("meters", "test.meter",
+ "Count",
+ "MeanRate",
+ "OneMinuteRate",
+ "FiveMinuteRate",
+ "FifteenMinuteRate",
+ "RateUnit");
assertThat(values(attributes))
.contains(entry("Count", 1L))
@@ -194,24 +211,24 @@ public class JmxReporterTest {
@Test
public void registersMBeansForTimers() throws Exception {
- final AttributeList attributes = getAttributes("test.another.timer",
- "Count",
- "MeanRate",
- "OneMinuteRate",
- "FiveMinuteRate",
- "FifteenMinuteRate",
- "Max",
- "Mean",
- "Min",
- "StdDev",
- "50thPercentile",
- "75thPercentile",
- "95thPercentile",
- "98thPercentile",
- "99thPercentile",
- "999thPercentile",
- "RateUnit",
- "DurationUnit");
+ final AttributeList attributes = getAttributes("timers", "test.another.timer",
+ "Count",
+ "MeanRate",
+ "OneMinuteRate",
+ "FiveMinuteRate",
+ "FifteenMinuteRate",
+ "Max",
+ "Mean",
+ "Min",
+ "StdDev",
+ "50thPercentile",
+ "75thPercentile",
+ "95thPercentile",
+ "98thPercentile",
+ "99thPercentile",
+ "999thPercentile",
+ "RateUnit",
+ "DurationUnit");
assertThat(values(attributes))
.contains(entry("Count", 1L))
@@ -238,36 +255,36 @@ public class JmxReporterTest {
reporter.stop();
try {
- getAttributes("gauge", "Value");
+ getAttributes("gauges", "gauge", "Value", "Number");
failBecauseExceptionWasNotThrown(InstanceNotFoundException.class);
} catch (InstanceNotFoundException e) {
}
}
-
+
@Test
public void objectNameModifyingMBeanServer() throws Exception {
- MBeanServer mockedMBeanServer = mock(MBeanServer.class);
-
- // overwrite the objectName
- when(mockedMBeanServer.registerMBean(any(Object.class), any(ObjectName.class))).thenReturn(new ObjectInstance("DOMAIN:key=value","className"));
-
- MetricRegistry testRegistry = new MetricRegistry();
- JmxReporter testJmxReporter = JmxReporter.forRegistry(testRegistry)
+ MBeanServer mockedMBeanServer = mock(MBeanServer.class);
+
+ // overwrite the objectName
+ when(mockedMBeanServer.registerMBean(any(Object.class), any(ObjectName.class))).thenReturn(new ObjectInstance("DOMAIN:key=value", "className"));
+
+ MetricRegistry testRegistry = new MetricRegistry();
+ JmxReporter testJmxReporter = JmxReporter.forRegistry(testRegistry)
.registerWith(mockedMBeanServer)
.inDomain(name)
.build();
-
- testJmxReporter.start();
-
- // should trigger a registerMBean
- testRegistry.timer("test");
-
- // should trigger an unregisterMBean with the overwritten objectName = "DOMAIN:key=value"
- testJmxReporter.stop();
-
- verify(mockedMBeanServer).unregisterMBean(new ObjectName("DOMAIN:key=value"));
-
+
+ testJmxReporter.start();
+
+ // should trigger a registerMBean
+ testRegistry.timer("test");
+
+ // should trigger an unregisterMBean with the overwritten objectName = "DOMAIN:key=value"
+ testJmxReporter.stop();
+
+ verify(mockedMBeanServer).unregisterMBean(new ObjectName("DOMAIN:key=value"));
+
}
@Test
@@ -277,13 +294,13 @@ public class JmxReporterTest {
metricRegistry.counter("test*");
}
- private AttributeList getAttributes(String name, String... attributeNames) throws JMException {
- ObjectName n = concreteObjectNameFactory.createName("only-for-logging-error", this.name, name);
+ private AttributeList getAttributes(String type, String name, String... attributeNames) throws JMException {
+ ObjectName n = concreteObjectNameFactory.createName(type, this.name, name);
return mBeanServer.getAttributes(n, attributeNames);
}
private SortedMap<String, Object> values(AttributeList attributes) {
- final TreeMap<String, Object> values = new TreeMap<String, Object>();
+ final TreeMap<String, Object> values = new TreeMap<>();
for (Object o : attributes) {
final Attribute attribute = (Attribute) o;
values.put(attribute.getName(), attribute.getValue());
diff --git a/metrics-json/pom.xml b/metrics-json/pom.xml
index 62f2f6c..f24ca39 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-json</artifactId>
@@ -15,22 +15,72 @@
A set of Jackson modules which provide serializers for most Metrics classes.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.json</javaModuleName>
+ <jackson.version>2.12.7</jackson.version>
+ <jackson-databind.version>2.12.7.1</jackson-databind.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-healthchecks</artifactId>
- <version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
+ <artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson-databind.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
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 61f068c..4a8041c 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
@@ -3,19 +3,20 @@ 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;
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 java.io.IOException;
-import java.util.Arrays;
-import java.util.Iterator;
+import java.util.Collections;
import java.util.Map;
public class HealthCheckModule extends Module {
private static class HealthCheckResultSerializer extends StdSerializer<HealthCheck.Result> {
+
+ private static final long serialVersionUID = 1L;
+
private HealthCheckResultSerializer() {
super(HealthCheck.Result.class);
}
@@ -33,22 +34,23 @@ public class HealthCheckModule extends Module {
}
serializeThrowable(json, result.getError(), "error");
+ json.writeNumberField("duration", result.getDuration());
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();
+ for (Map.Entry<String, Object> e : details.entrySet()) {
json.writeObjectField(e.getKey(), e.getValue());
}
}
+ json.writeStringField("timestamp", result.getTimestamp());
json.writeEndObject();
}
private void serializeThrowable(JsonGenerator json, Throwable error, String name) throws IOException {
if (error != null) {
json.writeObjectFieldStart(name);
+ json.writeStringField("type", error.getClass().getTypeName());
json.writeStringField("message", error.getMessage());
json.writeArrayFieldStart("stack");
for (StackTraceElement element : error.getStackTrace()) {
@@ -77,8 +79,6 @@ public class HealthCheckModule extends Module {
@Override
public void setupModule(SetupContext context) {
- context.addSerializers(new SimpleSerializers(Arrays.<JsonSerializer<?>>asList(
- new HealthCheckResultSerializer()
- )));
+ context.addSerializers(new SimpleSerializers(Collections.singletonList(new HealthCheckResultSerializer())));
}
}
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 4dcf46c..382881e 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
@@ -1,13 +1,19 @@
package com.codahale.metrics.json;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.Version;
-import com.fasterxml.jackson.databind.JsonSerializer;
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.*;
import java.io.IOException;
import java.util.Arrays;
@@ -15,9 +21,13 @@ import java.util.Locale;
import java.util.concurrent.TimeUnit;
public class MetricsModule extends Module {
- static final Version VERSION = new Version(3, 1, 3, "", "com.codahale.metrics", "metrics-json");
+ static final Version VERSION = new Version(4, 0, 0, "", "io.dropwizard.metrics", "metrics-json");
+ @SuppressWarnings("rawtypes")
private static class GaugeSerializer extends StdSerializer<Gauge> {
+
+ private static final long serialVersionUID = 1L;
+
private GaugeSerializer() {
super(Gauge.class);
}
@@ -39,6 +49,9 @@ public class MetricsModule extends Module {
}
private static class CounterSerializer extends StdSerializer<Counter> {
+
+ private static final long serialVersionUID = 1L;
+
private CounterSerializer() {
super(Counter.class);
}
@@ -54,6 +67,9 @@ public class MetricsModule extends Module {
}
private static class HistogramSerializer extends StdSerializer<Histogram> {
+
+ private static final long serialVersionUID = 1L;
+
private final boolean showSamples;
private HistogramSerializer(boolean showSamples) {
@@ -88,6 +104,9 @@ public class MetricsModule extends Module {
}
private static class MeterSerializer extends StdSerializer<Meter> {
+
+ private static final long serialVersionUID = 1L;
+
private final String rateUnit;
private final double rateFactor;
@@ -113,6 +132,9 @@ public class MetricsModule extends Module {
}
private static class TimerSerializer extends StdSerializer<Timer> {
+
+ private static final long serialVersionUID = 1L;
+
private final String rateUnit;
private final double rateFactor;
private final String durationUnit;
@@ -169,9 +191,11 @@ public class MetricsModule extends Module {
}
private static class MetricRegistrySerializer extends StdSerializer<MetricRegistry> {
-
+
+ private static final long serialVersionUID = 1L;
+
private final MetricFilter filter;
-
+
private MetricRegistrySerializer(MetricFilter filter) {
super(MetricRegistry.class);
this.filter = filter;
@@ -192,11 +216,11 @@ public class MetricsModule extends Module {
}
}
- private final TimeUnit rateUnit;
- private final TimeUnit durationUnit;
- private final boolean showSamples;
- private final MetricFilter filter;
-
+ protected final TimeUnit rateUnit;
+ protected final TimeUnit durationUnit;
+ protected final boolean showSamples;
+ protected final MetricFilter filter;
+
public MetricsModule(TimeUnit rateUnit, TimeUnit durationUnit, boolean showSamples) {
this(rateUnit, durationUnit, showSamples, MetricFilter.ALL);
}
@@ -220,7 +244,7 @@ public class MetricsModule extends Module {
@Override
public void setupModule(SetupContext context) {
- context.addSerializers(new SimpleSerializers(Arrays.<JsonSerializer<?>>asList(
+ context.addSerializers(new SimpleSerializers(Arrays.asList(
new GaugeSerializer(),
new CounterSerializer(),
new HistogramSerializer(showSamples),
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 cbbd557..8518003 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
@@ -1,7 +1,7 @@
package com.codahale.metrics.json;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.codahale.metrics.health.HealthCheck;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import java.math.BigDecimal;
@@ -10,86 +10,98 @@ 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;
public class HealthCheckModuleTest {
private final ObjectMapper mapper = new ObjectMapper().registerModule(new HealthCheckModule());
@Test
public void serializesAHealthyResult() throws Exception {
- assertThat(mapper.writeValueAsString(HealthCheck.Result.healthy()))
- .isEqualTo("{\"healthy\":true}");
+ HealthCheck.Result result = HealthCheck.Result.healthy();
+ assertThat(mapper.writeValueAsString(result))
+ .isEqualTo("{\"healthy\":true,\"duration\":0,\"timestamp\":\"" + result.getTimestamp() + "\"}");
}
@Test
public void serializesAHealthyResultWithAMessage() throws Exception {
- assertThat(mapper.writeValueAsString(HealthCheck.Result.healthy("yay for %s", "me")))
- .isEqualTo("{" +
- "\"healthy\":true," +
- "\"message\":\"yay for me\"}");
+ HealthCheck.Result result = HealthCheck.Result.healthy("yay for %s", "me");
+ assertThat(mapper.writeValueAsString(result))
+ .isEqualTo("{" +
+ "\"healthy\":true," +
+ "\"message\":\"yay for me\"," +
+ "\"duration\":0," +
+ "\"timestamp\":\"" + result.getTimestamp() + "\"" +
+ "}");
}
@Test
public void serializesAnUnhealthyResult() throws Exception {
- assertThat(mapper.writeValueAsString(HealthCheck.Result.unhealthy("boo")))
- .isEqualTo("{" +
- "\"healthy\":false," +
- "\"message\":\"boo\"}");
+ HealthCheck.Result result = HealthCheck.Result.unhealthy("boo");
+ assertThat(mapper.writeValueAsString(result))
+ .isEqualTo("{" +
+ "\"healthy\":false," +
+ "\"message\":\"boo\"," +
+ "\"duration\":0," +
+ "\"timestamp\":\"" + result.getTimestamp() + "\"" +
+ "}");
}
@Test
public void serializesAnUnhealthyResultWithAnException() throws Exception {
- final Throwable e = mock(Throwable.class);
- when(e.getMessage()).thenReturn("oh no");
- when(e.getStackTrace()).thenReturn(new StackTraceElement[]{
- new StackTraceElement("Blah", "bloo", "Blah.java", 100)
+ final RuntimeException e = new RuntimeException("oh no");
+ e.setStackTrace(new StackTraceElement[]{
+ new StackTraceElement("Blah", "bloo", "Blah.java", 100)
});
- assertThat(mapper.writeValueAsString(HealthCheck.Result.unhealthy(e)))
- .isEqualTo("{" +
- "\"healthy\":false," +
- "\"message\":\"oh no\"," +
- "\"error\":{" +
- "\"message\":\"oh no\"," +
- "\"stack\":[\"Blah.bloo(Blah.java:100)\"]" +
- "}" +
- "}");
+ HealthCheck.Result result = HealthCheck.Result.unhealthy(e);
+ assertThat(mapper.writeValueAsString(result))
+ .isEqualTo("{" +
+ "\"healthy\":false," +
+ "\"message\":\"oh no\"," +
+ "\"error\":{" +
+ "\"type\":\"java.lang.RuntimeException\"," +
+ "\"message\":\"oh no\"," +
+ "\"stack\":[\"Blah.bloo(Blah.java:100)\"]" +
+ "}," +
+ "\"duration\":0," +
+ "\"timestamp\":\"" + result.getTimestamp() + "\"" +
+ "}");
}
@Test
public void serializesAnUnhealthyResultWithNestedExceptions() throws Exception {
- final Throwable a = mock(Throwable.class);
- when(a.getMessage()).thenReturn("oh no");
- when(a.getStackTrace()).thenReturn(new StackTraceElement[]{
+ final RuntimeException a = new RuntimeException("oh no");
+ a.setStackTrace(new StackTraceElement[]{
new StackTraceElement("Blah", "bloo", "Blah.java", 100)
});
- final Throwable b = mock(Throwable.class);
- when(b.getMessage()).thenReturn("oh well");
- when(b.getStackTrace()).thenReturn(new StackTraceElement[]{
+ final RuntimeException b = new RuntimeException("oh well", a);
+ b.setStackTrace(new StackTraceElement[]{
new StackTraceElement("Blah", "blee", "Blah.java", 150)
});
- when(b.getCause()).thenReturn(a);
- assertThat(mapper.writeValueAsString(HealthCheck.Result.unhealthy(b)))
- .isEqualTo("{" +
- "\"healthy\":false," +
- "\"message\":\"oh well\"," +
- "\"error\":{" +
- "\"message\":\"oh well\"," +
- "\"stack\":[\"Blah.blee(Blah.java:150)\"]," +
- "\"cause\":{" +
- "\"message\":\"oh no\"," +
- "\"stack\":[\"Blah.bloo(Blah.java:100)\"]" +
- "}" +
- "}" +
- "}");
+ HealthCheck.Result result = HealthCheck.Result.unhealthy(b);
+ assertThat(mapper.writeValueAsString(result))
+ .isEqualTo("{" +
+ "\"healthy\":false," +
+ "\"message\":\"oh well\"," +
+ "\"error\":{" +
+ "\"type\":\"java.lang.RuntimeException\"," +
+ "\"message\":\"oh well\"," +
+ "\"stack\":[\"Blah.blee(Blah.java:150)\"]," +
+ "\"cause\":{" +
+ "\"type\":\"java.lang.RuntimeException\"," +
+ "\"message\":\"oh no\"," +
+ "\"stack\":[\"Blah.bloo(Blah.java:100)\"]" +
+ "}" +
+ "}," +
+ "\"duration\":0," +
+ "\"timestamp\":\"" + result.getTimestamp() + "\"" +
+ "}");
}
@Test
public void serializeResultWithDetail() throws Exception {
- Map<String, Object> complex = new LinkedHashMap<String, Object>();
+ Map<String, Object> complex = new LinkedHashMap<>();
complex.put("field", "value");
HealthCheck.Result result = HealthCheck.Result.builder()
@@ -108,6 +120,7 @@ public class HealthCheckModuleTest {
assertThat(mapper.writeValueAsString(result))
.isEqualTo("{" +
"\"healthy\":true," +
+ "\"duration\":0," +
"\"boolean\":true," +
"\"integer\":1," +
"\"long\":2," +
@@ -117,8 +130,9 @@ public class HealthCheckModuleTest {
"\"BigDecimal\":12345.56789," +
"\"String\":\"string\"," +
"\"complex\":{" +
- "\"field\":\"value\"" +
- "}" +
- "}");
+ "\"field\":\"value\"" +
+ "}," +
+ "\"timestamp\":\"" + result.getTimestamp() + "\"" +
+ "}");
}
}
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 bf162bd..10cebf5 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
@@ -1,7 +1,14 @@
package com.codahale.metrics.json;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.codahale.metrics.*;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
@@ -16,12 +23,7 @@ public class MetricsModuleTest {
@Test
public void serializesGauges() throws Exception {
- final Gauge<Integer> gauge = new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- return 100;
- }
- };
+ final Gauge<Integer> gauge = () -> 100;
assertThat(mapper.writeValueAsString(gauge))
.isEqualTo("{\"value\":100}");
@@ -29,11 +31,8 @@ public class MetricsModuleTest {
@Test
public void serializesGaugesThatThrowExceptions() throws Exception {
- final Gauge<Integer> gauge = new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- throw new IllegalArgumentException("poops");
- }
+ final Gauge<Integer> gauge = () -> {
+ throw new IllegalArgumentException("poops");
};
assertThat(mapper.writeValueAsString(gauge))
@@ -65,41 +64,41 @@ public class MetricsModuleTest {
when(snapshot.get98thPercentile()).thenReturn(9.0);
when(snapshot.get99thPercentile()).thenReturn(10.0);
when(snapshot.get999thPercentile()).thenReturn(11.0);
- when(snapshot.getValues()).thenReturn(new long[]{ 1, 2, 3 });
+ when(snapshot.getValues()).thenReturn(new long[]{1, 2, 3});
when(histogram.getSnapshot()).thenReturn(snapshot);
assertThat(mapper.writeValueAsString(histogram))
.isEqualTo("{" +
- "\"count\":1," +
- "\"max\":2," +
- "\"mean\":3.0," +
- "\"min\":4," +
- "\"p50\":6.0," +
- "\"p75\":7.0," +
- "\"p95\":8.0," +
- "\"p98\":9.0," +
- "\"p99\":10.0," +
- "\"p999\":11.0," +
- "\"stddev\":5.0}");
+ "\"count\":1," +
+ "\"max\":2," +
+ "\"mean\":3.0," +
+ "\"min\":4," +
+ "\"p50\":6.0," +
+ "\"p75\":7.0," +
+ "\"p95\":8.0," +
+ "\"p98\":9.0," +
+ "\"p99\":10.0," +
+ "\"p999\":11.0," +
+ "\"stddev\":5.0}");
final ObjectMapper fullMapper = new ObjectMapper().registerModule(
new MetricsModule(TimeUnit.SECONDS, TimeUnit.MILLISECONDS, true, MetricFilter.ALL));
assertThat(fullMapper.writeValueAsString(histogram))
.isEqualTo("{" +
- "\"count\":1," +
- "\"max\":2," +
- "\"mean\":3.0," +
- "\"min\":4," +
- "\"p50\":6.0," +
- "\"p75\":7.0," +
- "\"p95\":8.0," +
- "\"p98\":9.0," +
- "\"p99\":10.0," +
- "\"p999\":11.0," +
- "\"values\":[1,2,3]," +
- "\"stddev\":5.0}");
+ "\"count\":1," +
+ "\"max\":2," +
+ "\"mean\":3.0," +
+ "\"min\":4," +
+ "\"p50\":6.0," +
+ "\"p75\":7.0," +
+ "\"p95\":8.0," +
+ "\"p98\":9.0," +
+ "\"p99\":10.0," +
+ "\"p999\":11.0," +
+ "\"values\":[1,2,3]," +
+ "\"stddev\":5.0}");
}
@Test
@@ -113,12 +112,12 @@ public class MetricsModuleTest {
assertThat(mapper.writeValueAsString(meter))
.isEqualTo("{" +
- "\"count\":1," +
- "\"m15_rate\":3.0," +
- "\"m1_rate\":5.0," +
- "\"m5_rate\":4.0," +
- "\"mean_rate\":2.0," +
- "\"units\":\"events/second\"}");
+ "\"count\":1," +
+ "\"m15_rate\":3.0," +
+ "\"m1_rate\":5.0," +
+ "\"m5_rate\":4.0," +
+ "\"mean_rate\":2.0," +
+ "\"units\":\"events/second\"}");
}
@Test
@@ -152,47 +151,47 @@ public class MetricsModuleTest {
assertThat(mapper.writeValueAsString(timer))
.isEqualTo("{" +
- "\"count\":1," +
- "\"max\":100.0," +
- "\"mean\":200.0," +
- "\"min\":300.0," +
- "\"p50\":500.0," +
- "\"p75\":600.0," +
- "\"p95\":700.0," +
- "\"p98\":800.0," +
- "\"p99\":900.0," +
- "\"p999\":1000.0," +
- "\"stddev\":400.0," +
- "\"m15_rate\":5.0," +
- "\"m1_rate\":3.0," +
- "\"m5_rate\":4.0," +
- "\"mean_rate\":2.0," +
- "\"duration_units\":\"milliseconds\"," +
- "\"rate_units\":\"calls/second\"}");
+ "\"count\":1," +
+ "\"max\":100.0," +
+ "\"mean\":200.0," +
+ "\"min\":300.0," +
+ "\"p50\":500.0," +
+ "\"p75\":600.0," +
+ "\"p95\":700.0," +
+ "\"p98\":800.0," +
+ "\"p99\":900.0," +
+ "\"p999\":1000.0," +
+ "\"stddev\":400.0," +
+ "\"m15_rate\":5.0," +
+ "\"m1_rate\":3.0," +
+ "\"m5_rate\":4.0," +
+ "\"mean_rate\":2.0," +
+ "\"duration_units\":\"milliseconds\"," +
+ "\"rate_units\":\"calls/second\"}");
final ObjectMapper fullMapper = new ObjectMapper().registerModule(
new MetricsModule(TimeUnit.SECONDS, TimeUnit.MILLISECONDS, true, MetricFilter.ALL));
assertThat(fullMapper.writeValueAsString(timer))
.isEqualTo("{" +
- "\"count\":1," +
- "\"max\":100.0," +
- "\"mean\":200.0," +
- "\"min\":300.0," +
- "\"p50\":500.0," +
- "\"p75\":600.0," +
- "\"p95\":700.0," +
- "\"p98\":800.0," +
- "\"p99\":900.0," +
- "\"p999\":1000.0," +
- "\"values\":[1.0,2.0,3.0]," +
- "\"stddev\":400.0," +
- "\"m15_rate\":5.0," +
- "\"m1_rate\":3.0," +
- "\"m5_rate\":4.0," +
- "\"mean_rate\":2.0," +
- "\"duration_units\":\"milliseconds\"," +
- "\"rate_units\":\"calls/second\"}");
+ "\"count\":1," +
+ "\"max\":100.0," +
+ "\"mean\":200.0," +
+ "\"min\":300.0," +
+ "\"p50\":500.0," +
+ "\"p75\":600.0," +
+ "\"p95\":700.0," +
+ "\"p98\":800.0," +
+ "\"p99\":900.0," +
+ "\"p999\":1000.0," +
+ "\"values\":[1.0,2.0,3.0]," +
+ "\"stddev\":400.0," +
+ "\"m15_rate\":5.0," +
+ "\"m1_rate\":3.0," +
+ "\"m5_rate\":4.0," +
+ "\"mean_rate\":2.0," +
+ "\"duration_units\":\"milliseconds\"," +
+ "\"rate_units\":\"calls/second\"}");
}
@Test
@@ -201,11 +200,11 @@ public class MetricsModuleTest {
assertThat(mapper.writeValueAsString(registry))
.isEqualTo("{" +
- "\"version\":\"3.1.3\"," +
- "\"gauges\":{}," +
- "\"counters\":{}," +
- "\"histograms\":{}," +
- "\"meters\":{}," +
- "\"timers\":{}}");
+ "\"version\":\"4.0.0\"," +
+ "\"gauges\":{}," +
+ "\"counters\":{}," +
+ "\"histograms\":{}," +
+ "\"meters\":{}," +
+ "\"timers\":{}}");
}
}
diff --git a/metrics-jvm/pom.xml b/metrics-jvm/pom.xml
index 7c1ff70..2aeaf23 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-jvm</artifactId>
@@ -16,11 +16,60 @@
using Metrics.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.jvm</javaModuleName>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
</dependency>
</dependencies>
</project>
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/BufferPoolMetricSet.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/BufferPoolMetricSet.java
index db82391..b7f64b3 100644
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/BufferPoolMetricSet.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/BufferPoolMetricSet.java
@@ -1,6 +1,5 @@
package com.codahale.metrics.jvm;
-import com.codahale.metrics.JmxAttributeGauge;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricSet;
import org.slf4j.Logger;
@@ -22,9 +21,9 @@ import static com.codahale.metrics.MetricRegistry.name;
*/
public class BufferPoolMetricSet implements MetricSet {
private static final Logger LOGGER = LoggerFactory.getLogger(BufferPoolMetricSet.class);
- private static final String[] ATTRIBUTES = { "Count", "MemoryUsed", "TotalCapacity" };
- private static final String[] NAMES = { "count", "used", "capacity" };
- private static final String[] POOLS = { "direct", "mapped" };
+ private static final String[] ATTRIBUTES = {"Count", "MemoryUsed", "TotalCapacity"};
+ private static final String[] NAMES = {"count", "used", "capacity"};
+ private static final String[] POOLS = {"direct", "mapped"};
private final MBeanServer mBeanServer;
@@ -34,7 +33,7 @@ public class BufferPoolMetricSet implements MetricSet {
@Override
public Map<String, Metric> getMetrics() {
- final Map<String, Metric> gauges = new HashMap<String, Metric>();
+ final Map<String, Metric> gauges = new HashMap<>();
for (String pool : POOLS) {
for (int i = 0; i < ATTRIBUTES.length; i++) {
final String attribute = ATTRIBUTES[i];
@@ -42,8 +41,7 @@ public class BufferPoolMetricSet implements MetricSet {
try {
final ObjectName on = new ObjectName("java.nio:type=BufferPool,name=" + pool);
mBeanServer.getMBeanInfo(on);
- gauges.put(name(pool, name),
- new JmxAttributeGauge(mBeanServer, on, attribute));
+ gauges.put(name(pool, name), new JmxAttributeGauge(mBeanServer, on, attribute));
} catch (JMException ignored) {
LOGGER.debug("Unable to load buffer pool MBeans, possibly running on Java 6");
}
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/CachedThreadStatesGaugeSet.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/CachedThreadStatesGaugeSet.java
index b6bbad0..f2fd8c2 100644
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/CachedThreadStatesGaugeSet.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/CachedThreadStatesGaugeSet.java
@@ -29,7 +29,7 @@ public class CachedThreadStatesGaugeSet extends ThreadStatesGaugeSet {
super(threadMXBean, deadlockDetector);
threadInfo = new CachedGauge<ThreadInfo[]>(interval, unit) {
@Override
- protected ThreadInfo[] loadValue() {
+ protected ThreadInfo[] loadValue() {
return CachedThreadStatesGaugeSet.super.getThreadInfo();
}
};
@@ -38,8 +38,9 @@ public class CachedThreadStatesGaugeSet extends ThreadStatesGaugeSet {
/**
* Creates a new set of gauges using the default MXBeans.
* Caches the information for the given interval and time unit.
- * @param interval cache interval
- * @param unit cache interval time unit
+ *
+ * @param interval cache interval
+ * @param unit cache interval time unit
*/
public CachedThreadStatesGaugeSet(long interval, TimeUnit unit) {
this(ManagementFactory.getThreadMXBean(), new ThreadDeadlockDetector(), interval, unit);
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ClassLoadingGaugeSet.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ClassLoadingGaugeSet.java
index 0e5c6b3..89def18 100644
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ClassLoadingGaugeSet.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ClassLoadingGaugeSet.java
@@ -26,21 +26,9 @@ public class ClassLoadingGaugeSet implements MetricSet {
@Override
public Map<String, Metric> getMetrics() {
- final Map<String, Metric> gauges = new HashMap<String, Metric>();
-
- gauges.put("loaded", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getTotalLoadedClassCount();
- }
- });
-
- gauges.put("unloaded", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getUnloadedClassCount();
- }
- });
+ final Map<String, Metric> gauges = new HashMap<>();
+ gauges.put("loaded", (Gauge<Long>) mxBean::getTotalLoadedClassCount);
+ gauges.put("unloaded", (Gauge<Long>) mxBean::getUnloadedClassCount);
return gauges;
}
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/CpuTimeClock.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/CpuTimeClock.java
new file mode 100644
index 0000000..1385308
--- /dev/null
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/CpuTimeClock.java
@@ -0,0 +1,19 @@
+package com.codahale.metrics.jvm;
+
+import com.codahale.metrics.Clock;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+
+/**
+ * A clock implementation which returns the current thread's CPU time.
+ */
+public class CpuTimeClock extends Clock {
+
+ private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean();
+
+ @Override
+ public long getTick() {
+ return THREAD_MX_BEAN.getCurrentThreadCpuTime();
+ }
+}
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/FileDescriptorRatioGauge.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/FileDescriptorRatioGauge.java
index d6f2bb3..4b51479 100644
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/FileDescriptorRatioGauge.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/FileDescriptorRatioGauge.java
@@ -4,15 +4,24 @@ import com.codahale.metrics.RatioGauge;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
/**
* A gauge for the ratio of used to total file descriptors.
*/
public class FileDescriptorRatioGauge extends RatioGauge {
+ private static boolean unixOperatingSystemMXBeanExists = false;
+
private final OperatingSystemMXBean os;
+ static {
+ try {
+ Class.forName("com.sun.management.UnixOperatingSystemMXBean");
+ unixOperatingSystemMXBeanExists = true;
+ } catch (ClassNotFoundException e) {
+ // do nothing
+ }
+ }
+
/**
* Creates a new gauge using the platform OS bean.
*/
@@ -23,7 +32,7 @@ public class FileDescriptorRatioGauge extends RatioGauge {
/**
* Creates a new gauge using the given OS bean.
*
- * @param os an {@link OperatingSystemMXBean}
+ * @param os an {@link OperatingSystemMXBean}
*/
public FileDescriptorRatioGauge(OperatingSystemMXBean os) {
this.os = os;
@@ -31,21 +40,11 @@ public class FileDescriptorRatioGauge extends RatioGauge {
@Override
protected Ratio getRatio() {
- try {
- return Ratio.of(invoke("getOpenFileDescriptorCount"),
- invoke("getMaxFileDescriptorCount"));
- } catch (NoSuchMethodException e) {
- return Ratio.of(Double.NaN, Double.NaN);
- } catch (IllegalAccessException e) {
- return Ratio.of(Double.NaN, Double.NaN);
- } catch (InvocationTargetException e) {
+ if (unixOperatingSystemMXBeanExists && os instanceof com.sun.management.UnixOperatingSystemMXBean) {
+ final com.sun.management.UnixOperatingSystemMXBean unixOs = (com.sun.management.UnixOperatingSystemMXBean) os;
+ return Ratio.of(unixOs.getOpenFileDescriptorCount(), unixOs.getMaxFileDescriptorCount());
+ } else {
return Ratio.of(Double.NaN, Double.NaN);
}
}
-
- private long invoke(String name) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- final Method method = os.getClass().getDeclaredMethod(name);
- method.setAccessible(true);
- return (Long) method.invoke(os);
- }
}
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/GarbageCollectorMetricSet.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/GarbageCollectorMetricSet.java
index 90741fc..705f6e0 100644
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/GarbageCollectorMetricSet.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/GarbageCollectorMetricSet.java
@@ -6,7 +6,12 @@ import com.codahale.metrics.MetricSet;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.regex.Pattern;
import static com.codahale.metrics.MetricRegistry.name;
@@ -29,30 +34,19 @@ public class GarbageCollectorMetricSet implements MetricSet {
/**
* Creates a new set of gauges for the given collection of garbage collectors.
*
- * @param garbageCollectors the garbage collectors
+ * @param garbageCollectors the garbage collectors
*/
public GarbageCollectorMetricSet(Collection<GarbageCollectorMXBean> garbageCollectors) {
- this.garbageCollectors = new ArrayList<GarbageCollectorMXBean>(garbageCollectors);
+ this.garbageCollectors = new ArrayList<>(garbageCollectors);
}
@Override
public Map<String, Metric> getMetrics() {
- final Map<String, Metric> gauges = new HashMap<String, Metric>();
+ final Map<String, Metric> gauges = new HashMap<>();
for (final GarbageCollectorMXBean gc : garbageCollectors) {
final String name = WHITESPACE.matcher(gc.getName()).replaceAll("-");
- gauges.put(name(name, "count"), new Gauge<Long>() {
- @Override
- public Long getValue() {
- return gc.getCollectionCount();
- }
- });
-
- gauges.put(name(name, "time"), new Gauge<Long>() {
- @Override
- public Long getValue() {
- return gc.getCollectionTime();
- }
- });
+ gauges.put(name(name, "count"), (Gauge<Long>) gc::getCollectionCount);
+ gauges.put(name(name, "time"), (Gauge<Long>) gc::getCollectionTime);
}
return Collections.unmodifiableMap(gauges);
}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/JmxAttributeGauge.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/JmxAttributeGauge.java
similarity index 84%
rename from metrics-core/src/main/java/com/codahale/metrics/JmxAttributeGauge.java
rename to metrics-jvm/src/main/java/com/codahale/metrics/jvm/JmxAttributeGauge.java
index b49fa16..46ef9fe 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/JmxAttributeGauge.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/JmxAttributeGauge.java
@@ -1,4 +1,6 @@
-package com.codahale.metrics;
+package com.codahale.metrics.jvm;
+
+import com.codahale.metrics.Gauge;
import java.io.IOException;
import javax.management.JMException;
@@ -28,9 +30,9 @@ public class JmxAttributeGauge implements Gauge<Object> {
/**
* Creates a new JmxAttributeGauge.
*
- * @param mBeanServerConn the {@link MBeanServerConnection}
- * @param objectName the name of the object
- * @param attributeName the name of the object's attribute
+ * @param mBeanServerConn the {@link MBeanServerConnection}
+ * @param objectName the name of the object
+ * @param attributeName the name of the object's attribute
*/
public JmxAttributeGauge(MBeanServerConnection mBeanServerConn, ObjectName objectName, String attributeName) {
this.mBeanServerConn = mBeanServerConn;
@@ -42,9 +44,7 @@ public class JmxAttributeGauge implements Gauge<Object> {
public Object getValue() {
try {
return mBeanServerConn.getAttribute(getObjectName(), attributeName);
- } catch (IOException e) {
- return null;
- } catch (JMException e) {
+ } catch (IOException | JMException e) {
return null;
}
}
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/JvmAttributeGaugeSet.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/JvmAttributeGaugeSet.java
new file mode 100644
index 0000000..308ecc7
--- /dev/null
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/JvmAttributeGaugeSet.java
@@ -0,0 +1,51 @@
+package com.codahale.metrics.jvm;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricSet;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * A set of gauges for the JVM name, vendor, and uptime.
+ */
+public class JvmAttributeGaugeSet implements MetricSet {
+ private final RuntimeMXBean runtime;
+
+ /**
+ * Creates a new set of gauges.
+ */
+ public JvmAttributeGaugeSet() {
+ this(ManagementFactory.getRuntimeMXBean());
+ }
+
+ /**
+ * Creates a new set of gauges with the given {@link RuntimeMXBean}.
+ *
+ * @param runtime JVM management interface with access to system properties
+ */
+ public JvmAttributeGaugeSet(RuntimeMXBean runtime) {
+ this.runtime = runtime;
+ }
+
+ @Override
+ public Map<String, Metric> getMetrics() {
+ final Map<String, Metric> gauges = new HashMap<>();
+
+ gauges.put("name", (Gauge<String>) runtime::getName);
+ gauges.put("vendor", (Gauge<String>) () -> String.format(Locale.US,
+ "%s %s %s (%s)",
+ runtime.getVmVendor(),
+ runtime.getVmName(),
+ runtime.getVmVersion(),
+ runtime.getSpecVersion()));
+ gauges.put("uptime", (Gauge<Long>) runtime::getUptime);
+
+ return Collections.unmodifiableMap(gauges);
+ }
+}
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 949e948..933b720 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
@@ -9,7 +9,12 @@ import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.regex.Pattern;
import static com.codahale.metrics.MetricRegistry.name;
@@ -25,81 +30,32 @@ public class MemoryUsageGaugeSet implements MetricSet {
private final List<MemoryPoolMXBean> memoryPools;
public MemoryUsageGaugeSet() {
- this(ManagementFactory.getMemoryMXBean(),
- ManagementFactory.getMemoryPoolMXBeans());
+ this(ManagementFactory.getMemoryMXBean(), ManagementFactory.getMemoryPoolMXBeans());
}
public MemoryUsageGaugeSet(MemoryMXBean mxBean,
Collection<MemoryPoolMXBean> memoryPools) {
this.mxBean = mxBean;
- this.memoryPools = new ArrayList<MemoryPoolMXBean>(memoryPools);
+ this.memoryPools = new ArrayList<>(memoryPools);
}
@Override
public Map<String, Metric> getMetrics() {
- final Map<String, Metric> gauges = new HashMap<String, Metric>();
-
- gauges.put("total.init", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getHeapMemoryUsage().getInit() +
- mxBean.getNonHeapMemoryUsage().getInit();
- }
- });
-
- gauges.put("total.used", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getHeapMemoryUsage().getUsed() +
- mxBean.getNonHeapMemoryUsage().getUsed();
- }
- });
-
- gauges.put("total.max", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getHeapMemoryUsage().getMax() +
- mxBean.getNonHeapMemoryUsage().getMax();
- }
- });
-
- gauges.put("total.committed", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getHeapMemoryUsage().getCommitted() +
- mxBean.getNonHeapMemoryUsage().getCommitted();
- }
- });
-
-
- gauges.put("heap.init", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getHeapMemoryUsage().getInit();
- }
- });
-
- gauges.put("heap.used", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getHeapMemoryUsage().getUsed();
- }
- });
-
- gauges.put("heap.max", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getHeapMemoryUsage().getMax();
- }
- });
-
- gauges.put("heap.committed", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getHeapMemoryUsage().getCommitted();
- }
- });
-
+ final Map<String, Metric> gauges = new HashMap<>();
+
+ gauges.put("total.init", (Gauge<Long>) () -> mxBean.getHeapMemoryUsage().getInit() +
+ mxBean.getNonHeapMemoryUsage().getInit());
+ gauges.put("total.used", (Gauge<Long>) () -> mxBean.getHeapMemoryUsage().getUsed() +
+ mxBean.getNonHeapMemoryUsage().getUsed());
+ gauges.put("total.max", (Gauge<Long>) () -> mxBean.getNonHeapMemoryUsage().getMax() == -1 ?
+ -1 : mxBean.getHeapMemoryUsage().getMax() + mxBean.getNonHeapMemoryUsage().getMax());
+ gauges.put("total.committed", (Gauge<Long>) () -> mxBean.getHeapMemoryUsage().getCommitted() +
+ mxBean.getNonHeapMemoryUsage().getCommitted());
+
+ gauges.put("heap.init", (Gauge<Long>) () -> mxBean.getHeapMemoryUsage().getInit());
+ gauges.put("heap.used", (Gauge<Long>) () -> mxBean.getHeapMemoryUsage().getUsed());
+ gauges.put("heap.max", (Gauge<Long>) () -> mxBean.getHeapMemoryUsage().getMax());
+ gauges.put("heap.committed", (Gauge<Long>) () -> mxBean.getHeapMemoryUsage().getCommitted());
gauges.put("heap.usage", new RatioGauge() {
@Override
protected Ratio getRatio() {
@@ -108,92 +64,41 @@ public class MemoryUsageGaugeSet implements MetricSet {
}
});
- gauges.put("non-heap.init", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getNonHeapMemoryUsage().getInit();
- }
- });
-
- gauges.put("non-heap.used", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getNonHeapMemoryUsage().getUsed();
- }
- });
-
- gauges.put("non-heap.max", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getNonHeapMemoryUsage().getMax();
- }
- });
-
- gauges.put("non-heap.committed", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return mxBean.getNonHeapMemoryUsage().getCommitted();
- }
- });
-
+ gauges.put("non-heap.init", (Gauge<Long>) () -> mxBean.getNonHeapMemoryUsage().getInit());
+ gauges.put("non-heap.used", (Gauge<Long>) () -> mxBean.getNonHeapMemoryUsage().getUsed());
+ gauges.put("non-heap.max", (Gauge<Long>) () -> mxBean.getNonHeapMemoryUsage().getMax());
+ gauges.put("non-heap.committed", (Gauge<Long>) () -> mxBean.getNonHeapMemoryUsage().getCommitted());
gauges.put("non-heap.usage", new RatioGauge() {
@Override
protected Ratio getRatio() {
final MemoryUsage usage = mxBean.getNonHeapMemoryUsage();
- return Ratio.of(usage.getUsed(), usage.getMax());
+ return Ratio.of(usage.getUsed(), usage.getMax() == -1 ? usage.getCommitted() : usage.getMax());
}
});
for (final MemoryPoolMXBean pool : memoryPools) {
final String poolName = name("pools", WHITESPACE.matcher(pool.getName()).replaceAll("-"));
- gauges.put(name(poolName, "usage"),
- new RatioGauge() {
- @Override
- protected Ratio getRatio() {
- MemoryUsage usage = pool.getUsage();
- return Ratio.of(usage.getUsed(),
- usage.getMax() == -1 ? usage.getCommitted() : usage.getMax());
- }
- });
-
- gauges.put(name(poolName, "max"),new Gauge<Long>() {
+ gauges.put(name(poolName, "usage"), new RatioGauge() {
@Override
- public Long getValue() {
- return pool.getUsage().getMax();
+ protected Ratio getRatio() {
+ MemoryUsage usage = pool.getUsage();
+ return Ratio.of(usage.getUsed(),
+ usage.getMax() == -1 ? usage.getCommitted() : usage.getMax());
}
});
- gauges.put(name(poolName, "used"),new Gauge<Long>() {
- @Override
- public Long getValue() {
- return pool.getUsage().getUsed();
- }
- });
-
- gauges.put(name(poolName, "committed"),new Gauge<Long>() {
- @Override
- public Long getValue() {
- return pool.getUsage().getCommitted();
- }
- });
+ gauges.put(name(poolName, "max"), (Gauge<Long>) () -> pool.getUsage().getMax());
+ gauges.put(name(poolName, "used"), (Gauge<Long>) () -> pool.getUsage().getUsed());
+ gauges.put(name(poolName, "committed"), (Gauge<Long>) () -> pool.getUsage().getCommitted());
// 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, "used-after-gc"), (Gauge<Long>) () ->
+ pool.getCollectionUsage().getUsed());
}
- gauges.put(name(poolName, "init"),new Gauge<Long>() {
- @Override
- public Long getValue() {
- return pool.getUsage().getInit();
- }
- });
+ gauges.put(name(poolName, "init"), (Gauge<Long>) () -> pool.getUsage().getInit());
}
return Collections.unmodifiableMap(gauges);
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDeadlockDetector.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDeadlockDetector.java
index e6dd697..4a9ed22 100644
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDeadlockDetector.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDeadlockDetector.java
@@ -25,7 +25,7 @@ public class ThreadDeadlockDetector {
/**
* Creates a new detector using the given {@link ThreadMXBean}.
*
- * @param threads a {@link ThreadMXBean}
+ * @param threads a {@link ThreadMXBean}
*/
public ThreadDeadlockDetector(ThreadMXBean threads) {
this.threads = threads;
@@ -40,21 +40,21 @@ public class ThreadDeadlockDetector {
public Set<String> getDeadlockedThreads() {
final long[] ids = threads.findDeadlockedThreads();
if (ids != null) {
- final Set<String> deadlocks = new HashSet<String>();
+ final Set<String> deadlocks = new HashSet<>();
for (ThreadInfo info : threads.getThreadInfo(ids, MAX_STACK_TRACE_DEPTH)) {
final StringBuilder stackTrace = new StringBuilder();
for (StackTraceElement element : info.getStackTrace()) {
stackTrace.append("\t at ")
- .append(element.toString())
- .append(String.format("%n"));
+ .append(element.toString())
+ .append(String.format("%n"));
}
deadlocks.add(
String.format("%s locked on %s (owned by %s):%n%s",
- info.getThreadName(),
- info.getLockName(),
- info.getLockOwnerName(),
- stackTrace.toString()
+ info.getThreadName(),
+ info.getLockName(),
+ info.getLockOwnerName(),
+ stackTrace.toString()
)
);
}
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
index a001961..6ca1d7d 100755
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDump.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDump.java
@@ -7,13 +7,14 @@ import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
-import java.nio.charset.Charset;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
/**
* A convenience class for getting a thread dump.
*/
public class ThreadDump {
- private static final Charset UTF_8 = Charset.forName("UTF-8");
private final ThreadMXBean threadMXBean;
@@ -22,32 +23,49 @@ public class ThreadDump {
}
/**
- * Dumps all of the threads' current information to an output stream.
+ * Dumps all of the threads' current information, including synchronization, to an output stream.
*
* @param out an output stream
*/
public void dump(OutputStream out) {
- final ThreadInfo[] threads = this.threadMXBean.dumpAllThreads(true, true);
+ dump(true, true, out);
+ }
+
+ /**
+ * Dumps all of the threads' current information, optionally including synchronization, to an output stream.
+ *
+ * Having control over including synchronization info allows using this method (and its wrappers, i.e.
+ * ThreadDumpServlet) in environments where getting object monitor and/or ownable synchronizer usage is not
+ * supported. It can also speed things up.
+ *
+ * See {@link ThreadMXBean#dumpAllThreads(boolean, boolean)}
+ *
+ * @param lockedMonitors dump all locked monitors if true
+ * @param lockedSynchronizers dump all locked ownable synchronizers if true
+ * @param out an output stream
+ */
+ public void dump(boolean lockedMonitors, boolean lockedSynchronizers, OutputStream out) {
+ final ThreadInfo[] threads = this.threadMXBean.dumpAllThreads(lockedMonitors, lockedSynchronizers);
final PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, UTF_8));
for (int ti = threads.length - 1; ti >= 0; ti--) {
final ThreadInfo t = threads[ti];
writer.printf("\"%s\" id=%d state=%s",
- t.getThreadName(),
- t.getThreadId(),
- t.getThreadState());
+ t.getThreadName(),
+ t.getThreadId(),
+ t.getThreadState());
final LockInfo lock = t.getLockInfo();
if (lock != null && t.getThreadState() != Thread.State.BLOCKED) {
writer.printf("%n - waiting on <0x%08x> (a %s)",
- lock.getIdentityHashCode(),
- lock.getClassName());
+ lock.getIdentityHashCode(),
+ lock.getClassName());
writer.printf("%n - locked <0x%08x> (a %s)",
- lock.getIdentityHashCode(),
- lock.getClassName());
+ lock.getIdentityHashCode(),
+ lock.getClassName());
} else if (lock != null && t.getThreadState() == Thread.State.BLOCKED) {
writer.printf("%n - waiting to lock <0x%08x> (a %s)",
- lock.getIdentityHashCode(),
- lock.getClassName());
+ lock.getIdentityHashCode(),
+ lock.getClassName());
}
if (t.isSuspended()) {
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadStatesGaugeSet.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadStatesGaugeSet.java
index a79d6bc..9b796a7 100644
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadStatesGaugeSet.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadStatesGaugeSet.java
@@ -46,45 +46,19 @@ public class ThreadStatesGaugeSet implements MetricSet {
@Override
public Map<String, Metric> getMetrics() {
- final Map<String, Metric> gauges = new HashMap<String, Metric>();
+ final Map<String, Metric> gauges = new HashMap<>();
for (final Thread.State state : Thread.State.values()) {
gauges.put(name(state.toString().toLowerCase(), "count"),
- new Gauge<Object>() {
- @Override
- public Object getValue() {
- return getThreadCount(state);
- }
- });
+ (Gauge<Object>) () -> getThreadCount(state));
}
- gauges.put("count", new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- return threads.getThreadCount();
- }
- });
-
- gauges.put("daemon.count", new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- return threads.getDaemonThreadCount();
- }
- });
-
- gauges.put("deadlock.count", new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- return deadlockDetector.getDeadlockedThreads().size();
- }
- });
-
- gauges.put("deadlocks", new Gauge<Set<String>>() {
- @Override
- public Set<String> getValue() {
- return deadlockDetector.getDeadlockedThreads();
- }
- });
+ gauges.put("count", (Gauge<Integer>) threads::getThreadCount);
+ gauges.put("daemon.count", (Gauge<Integer>) threads::getDaemonThreadCount);
+ gauges.put("peak.count", (Gauge<Integer>) threads::getPeakThreadCount);
+ gauges.put("total_started.count", (Gauge<Long>) threads::getTotalStartedThreadCount);
+ gauges.put("deadlock.count", (Gauge<Integer>) () -> deadlockDetector.getDeadlockedThreads().size());
+ gauges.put("deadlocks", (Gauge<Set<String>>) deadlockDetector::getDeadlockedThreads);
return Collections.unmodifiableMap(gauges);
}
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/BufferPoolMetricSetTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/BufferPoolMetricSetTest.java
index 2f8aba3..f3d0cd9 100644
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/BufferPoolMetricSetTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/BufferPoolMetricSetTest.java
@@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+@SuppressWarnings("rawtypes")
public class BufferPoolMetricSetTest {
private final MBeanServer mBeanServer = mock(MBeanServer.class);
private final BufferPoolMetricSet buffers = new BufferPoolMetricSet(mBeanServer);
@@ -27,14 +28,14 @@ public class BufferPoolMetricSetTest {
}
@Test
- public void includesGaugesForDirectAndMappedPools() throws Exception {
+ public void includesGaugesForDirectAndMappedPools() {
assertThat(buffers.getMetrics().keySet())
.containsOnly("direct.count",
- "mapped.used",
- "mapped.capacity",
- "direct.capacity",
- "mapped.count",
- "direct.used");
+ "mapped.used",
+ "mapped.capacity",
+ "direct.capacity",
+ "mapped.count",
+ "direct.used");
}
@Test
@@ -43,8 +44,8 @@ public class BufferPoolMetricSetTest {
assertThat(buffers.getMetrics().keySet())
.containsOnly("direct.count",
- "direct.capacity",
- "direct.used");
+ "direct.capacity",
+ "direct.used");
}
@Test
@@ -76,7 +77,7 @@ public class BufferPoolMetricSetTest {
assertThat(gauge.getValue())
.isEqualTo(100);
}
-
+
@Test
public void includesAGaugeForMappedCount() throws Exception {
final Gauge gauge = (Gauge) buffers.getMetrics().get("mapped.count");
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ClassLoadingGaugeSetTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ClassLoadingGaugeSetTest.java
index b46a6dc..1597672 100644
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ClassLoadingGaugeSetTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ClassLoadingGaugeSetTest.java
@@ -10,25 +10,26 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+@SuppressWarnings("rawtypes")
public class ClassLoadingGaugeSetTest {
private final ClassLoadingMXBean cl = mock(ClassLoadingMXBean.class);
private final ClassLoadingGaugeSet gauges = new ClassLoadingGaugeSet(cl);
@Before
- public void setUp() throws Exception {
+ public void setUp() {
when(cl.getTotalLoadedClassCount()).thenReturn(2L);
when(cl.getUnloadedClassCount()).thenReturn(1L);
}
@Test
- public void loadedGauge() throws Exception {
+ public void loadedGauge() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("loaded");
assertThat(gauge.getValue()).isEqualTo(2L);
}
@Test
- public void unLoadedGauge() throws Exception {
+ public void unLoadedGauge() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("unloaded");
assertThat(gauge.getValue()).isEqualTo(1L);
}
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/CpuTimeClockTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/CpuTimeClockTest.java
new file mode 100644
index 0000000..f3a96d4
--- /dev/null
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/CpuTimeClockTest.java
@@ -0,0 +1,24 @@
+package com.codahale.metrics.jvm;
+
+import org.junit.Test;
+
+import java.lang.management.ManagementFactory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.offset;
+
+public class CpuTimeClockTest {
+
+ @Test
+ public void cpuTimeClock() {
+ final CpuTimeClock clock = new CpuTimeClock();
+
+ assertThat((double) clock.getTime())
+ .isEqualTo(System.currentTimeMillis(),
+ offset(250D));
+
+ assertThat((double) clock.getTick())
+ .isEqualTo(ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime(),
+ offset(1000000.0));
+ }
+}
\ No newline at end of file
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/FileDescriptorRatioGaugeSunManagementNotExistsTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/FileDescriptorRatioGaugeSunManagementNotExistsTest.java
new file mode 100644
index 0000000..48a514a
--- /dev/null
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/FileDescriptorRatioGaugeSunManagementNotExistsTest.java
@@ -0,0 +1,117 @@
+package com.codahale.metrics.jvm;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.PrivilegedAction;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+
+@RunWith(FileDescriptorRatioGaugeSunManagementNotExistsTest.SunManagementNotExistsTestRunner.class)
+public class FileDescriptorRatioGaugeSunManagementNotExistsTest {
+
+ @Test
+ public void validateFileDescriptorRatioWhenSunManagementNotExists() {
+ assertThat(new FileDescriptorRatioGauge().getValue()).isNaN();
+ }
+
+ public static class SunManagementNotExistsTestRunner extends BlockJUnit4ClassRunner {
+
+ public SunManagementNotExistsTestRunner(Class<?> clazz) throws InitializationError {
+ super(getFromSunManagementNotExistsClassLoader(clazz));
+ }
+
+ private static Class<?> getFromSunManagementNotExistsClassLoader(Class<?> clazz) throws InitializationError {
+ try {
+ return Class.forName(clazz.getName(), true,
+ new SunManagementNotExistsClassLoader(SunManagementNotExistsTestRunner.class.getClassLoader()));
+ } catch (ClassNotFoundException e) {
+ throw new InitializationError(e);
+ }
+ }
+ }
+
+ public static class SunManagementNotExistsClassLoader extends URLClassLoader {
+ private static final URL[] CLASSPATH_ENTRY_URLS;
+ private static final PermissionCollection NO_PERMS = new Permissions();
+
+ static {
+ String[] classpathEntries = AccessController.doPrivileged(new PrivilegedAction<String>() {
+ @Override
+ public String run() {
+ return System.getProperty("java.class.path");
+ }
+ }).split(File.pathSeparator);
+ CLASSPATH_ENTRY_URLS = getClasspathEntryUrls(classpathEntries);
+ }
+
+ private static URL[] getClasspathEntryUrls(String... classpathEntries) {
+ Set<URL> classpathEntryUrls = new LinkedHashSet<>(classpathEntries.length, 1);
+ for (String classpathEntry : classpathEntries) {
+ URL classpathEntryUrl = getClasspathEntryUrl(classpathEntry);
+ if (classpathEntryUrl != null) {
+ classpathEntryUrls.add(classpathEntryUrl);
+ }
+ }
+ return classpathEntryUrls.toArray(new URL[classpathEntryUrls.size()]);
+ }
+
+ private static URL getClasspathEntryUrl(String classpathEntry) {
+ try {
+ if (classpathEntry.endsWith(".jar")) {
+ return new URL("file:jar:" + classpathEntry);
+ }
+ if (!classpathEntry.endsWith("/")) {
+ classpathEntry = classpathEntry + "/";
+ }
+ return new URL("file:" + classpathEntry);
+ } catch (MalformedURLException mue) {
+ // do nothing
+ }
+ return null;
+ }
+
+ public SunManagementNotExistsClassLoader(ClassLoader parent) {
+ super(CLASSPATH_ENTRY_URLS, parent);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (getClass().getName().equals(name)) {
+ return getClass();
+ }
+ if (name.startsWith("com.sun.management.")) {
+ throw new ClassNotFoundException(name);
+ }
+ if (name.startsWith("com.codahale.metrics.jvm.")) {
+ return loadMetricsClasses(name);
+ }
+ return super.loadClass(name, resolve);
+ }
+
+ private Class<?> loadMetricsClasses(String name) throws ClassNotFoundException {
+ Class<?> ret = findLoadedClass(name);
+ if (ret != null) {
+ return ret;
+ }
+ return findClass(name);
+ }
+
+ @Override
+ protected PermissionCollection getPermissions(CodeSource codesource) {
+ return NO_PERMS;
+ }
+ }
+}
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/FileDescriptorRatioGaugeTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/FileDescriptorRatioGaugeTest.java
index 750e281..0599a7a 100644
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/FileDescriptorRatioGaugeTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/FileDescriptorRatioGaugeTest.java
@@ -1,5 +1,7 @@
package com.codahale.metrics.jvm;
+import com.sun.management.UnixOperatingSystemMXBean;
+import org.junit.Before;
import org.junit.Test;
import javax.management.ObjectName;
@@ -9,70 +11,38 @@ import java.lang.management.OperatingSystemMXBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
@SuppressWarnings("UnusedDeclaration")
public class FileDescriptorRatioGaugeTest {
- private final OperatingSystemMXBean os = new OperatingSystemMXBean() {
- @Override
- public String getName() {
- return null;
- }
+ private final UnixOperatingSystemMXBean os = mock(UnixOperatingSystemMXBean.class);
- @Override
- public String getArch() {
- return null;
- }
-
- @Override
- public String getVersion() {
- return null;
- }
-
- @Override
- public int getAvailableProcessors() {
- return 0;
- }
-
- @Override
- public double getSystemLoadAverage() {
- return 0;
- }
-
- // these duplicate methods from UnixOperatingSystem
-
- private long getOpenFileDescriptorCount() {
- return 10;
- }
-
- private long getMaxFileDescriptorCount() {
- return 100;
- }
-
- // overridden on Java 1.7; random crap on Java 1.6
- public ObjectName getObjectName() {
- return null;
- }
- };
private final FileDescriptorRatioGauge gauge = new FileDescriptorRatioGauge(os);
+ @Before
+ public void setUp() throws Exception {
+ when(os.getOpenFileDescriptorCount()).thenReturn(10L);
+ when(os.getMaxFileDescriptorCount()).thenReturn(100L);
+ }
+
@Test
- public void calculatesTheRatioOfUsedToTotalFileDescriptors() throws Exception {
+ public void calculatesTheRatioOfUsedToTotalFileDescriptors() {
assertThat(gauge.getValue())
.isEqualTo(0.1);
}
@Test
- public void validateFileDescriptorRatioPresenceOnNixPlatforms() throws Exception {
+ public void validateFileDescriptorRatioPresenceOnNixPlatforms() {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
assumeTrue(osBean instanceof com.sun.management.UnixOperatingSystemMXBean);
-
+
assertThat(new FileDescriptorRatioGauge().getValue())
.isGreaterThanOrEqualTo(0.0)
.isLessThanOrEqualTo(1.0);
}
@Test
- public void returnsNaNWhenTheInformationIsUnavailable() throws Exception {
+ public void returnsNaNWhenTheInformationIsUnavailable() {
assertThat(new FileDescriptorRatioGauge(mock(OperatingSystemMXBean.class)).getValue())
.isNaN();
}
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/GarbageCollectorMetricSetTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/GarbageCollectorMetricSetTest.java
index ff0e3d3..9c4734c 100644
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/GarbageCollectorMetricSetTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/GarbageCollectorMetricSetTest.java
@@ -5,45 +5,46 @@ import org.junit.Before;
import org.junit.Test;
import java.lang.management.GarbageCollectorMXBean;
-import java.util.Arrays;
+import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+@SuppressWarnings("unchecked")
public class GarbageCollectorMetricSetTest {
private final GarbageCollectorMXBean gc = mock(GarbageCollectorMXBean.class);
- private final GarbageCollectorMetricSet metrics = new GarbageCollectorMetricSet(Arrays.asList(gc));
+ private final GarbageCollectorMetricSet metrics = new GarbageCollectorMetricSet(Collections.singletonList(gc));
@Before
- public void setUp() throws Exception {
+ public void setUp() {
when(gc.getName()).thenReturn("PS OldGen");
when(gc.getCollectionCount()).thenReturn(1L);
when(gc.getCollectionTime()).thenReturn(2L);
}
@Test
- public void hasGaugesForGcCountsAndElapsedTimes() throws Exception {
+ public void hasGaugesForGcCountsAndElapsedTimes() {
assertThat(metrics.getMetrics().keySet())
.containsOnly("PS-OldGen.time", "PS-OldGen.count");
}
@Test
- public void hasAGaugeForGcCounts() throws Exception {
- final Gauge gauge = (Gauge) metrics.getMetrics().get("PS-OldGen.count");
+ public void hasAGaugeForGcCounts() {
+ final Gauge<Long> gauge = (Gauge<Long>) metrics.getMetrics().get("PS-OldGen.count");
assertThat(gauge.getValue())
.isEqualTo(1L);
}
@Test
- public void hasAGaugeForGcTimes() throws Exception {
- final Gauge gauge = (Gauge) metrics.getMetrics().get("PS-OldGen.time");
+ public void hasAGaugeForGcTimes() {
+ final Gauge<Long> gauge = (Gauge<Long>) metrics.getMetrics().get("PS-OldGen.time");
assertThat(gauge.getValue())
.isEqualTo(2L);
}
@Test
- public void autoDiscoversGCs() throws Exception {
+ public void autoDiscoversGCs() {
assertThat(new GarbageCollectorMetricSet().getMetrics().keySet())
.isNotEmpty();
}
diff --git a/metrics-core/src/test/java/com/codahale/metrics/JmxAttributeGaugeTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/JmxAttributeGaugeTest.java
similarity index 98%
rename from metrics-core/src/test/java/com/codahale/metrics/JmxAttributeGaugeTest.java
rename to metrics-jvm/src/test/java/com/codahale/metrics/jvm/JmxAttributeGaugeTest.java
index 02f77f6..c709a2c 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/JmxAttributeGaugeTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/JmxAttributeGaugeTest.java
@@ -1,4 +1,4 @@
-package com.codahale.metrics;
+package com.codahale.metrics.jvm;
import static org.assertj.core.api.Assertions.assertThat;
@@ -19,7 +19,7 @@ public class JmxAttributeGaugeTest {
private static MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
- private static List<ObjectName> registeredMBeans = new ArrayList<ObjectName>();
+ private static List<ObjectName> registeredMBeans = new ArrayList<>();
public interface JmxTestMBean {
Long getValue();
diff --git a/metrics-core/src/test/java/com/codahale/metrics/JvmAttributeGaugeSetTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/JvmAttributeGaugeSetTest.java
similarity index 63%
rename from metrics-core/src/test/java/com/codahale/metrics/JvmAttributeGaugeSetTest.java
rename to metrics-jvm/src/test/java/com/codahale/metrics/jvm/JvmAttributeGaugeSetTest.java
index 1251dfa..43f49bd 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/JvmAttributeGaugeSetTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/JvmAttributeGaugeSetTest.java
@@ -1,5 +1,6 @@
-package com.codahale.metrics;
+package com.codahale.metrics.jvm;
+import com.codahale.metrics.Gauge;
import org.junit.Before;
import org.junit.Test;
@@ -9,12 +10,13 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+@SuppressWarnings("unchecked")
public class JvmAttributeGaugeSetTest {
private final RuntimeMXBean runtime = mock(RuntimeMXBean.class);
private final JvmAttributeGaugeSet gauges = new JvmAttributeGaugeSet(runtime);
@Before
- public void setUp() throws Exception {
+ public void setUp() {
when(runtime.getName()).thenReturn("9928@example.com");
when(runtime.getVmVendor()).thenReturn("Oracle Corporation");
@@ -25,40 +27,39 @@ public class JvmAttributeGaugeSetTest {
}
@Test
- public void hasASetOfGauges() throws Exception {
+ public void hasASetOfGauges() {
assertThat(gauges.getMetrics().keySet())
.containsOnly("vendor", "name", "uptime");
}
@Test
- public void hasAGaugeForTheJVMName() throws Exception {
- final Gauge gauge = (Gauge) gauges.getMetrics().get("name");
+ public void hasAGaugeForTheJVMName() {
+ final Gauge<String> gauge = (Gauge<String>) gauges.getMetrics().get("name");
assertThat(gauge.getValue())
.isEqualTo("9928@example.com");
}
@Test
- public void hasAGaugeForTheJVMVendor() throws Exception {
- final Gauge gauge = (Gauge) gauges.getMetrics().get("vendor");
+ public void hasAGaugeForTheJVMVendor() {
+ final Gauge<String> gauge = (Gauge<String>) gauges.getMetrics().get("vendor");
assertThat(gauge.getValue())
.isEqualTo("Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.7-b01 (1.7)");
}
@Test
- public void hasAGaugeForTheJVMUptime() throws Exception {
- final Gauge gauge = (Gauge) gauges.getMetrics().get("uptime");
+ public void hasAGaugeForTheJVMUptime() {
+ final Gauge<Long> gauge = (Gauge<Long>) gauges.getMetrics().get("uptime");
assertThat(gauge.getValue())
.isEqualTo(100L);
}
@Test
- public void autoDiscoversTheRuntimeBean() throws Exception {
- final Gauge gauge = (Gauge) new JvmAttributeGaugeSet().getMetrics().get("uptime");
+ public void autoDiscoversTheRuntimeBean() {
+ final Gauge<Long> gauge = (Gauge<Long>) new JvmAttributeGaugeSet().getMetrics().get("uptime");
- assertThat((Long) gauge.getValue())
- .isPositive();
+ assertThat(gauge.getValue()).isPositive();
}
}
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 4929627..4e92369 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
@@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+@SuppressWarnings("rawtypes")
public class MemoryUsageGaugeSetTest {
private final MemoryUsage heap = mock(MemoryUsage.class);
private final MemoryUsage nonHeap = mock(MemoryUsage.class);
@@ -24,11 +25,11 @@ public class MemoryUsageGaugeSetTest {
private final MemoryPoolMXBean weirdMemoryPool = mock(MemoryPoolMXBean.class);
private final MemoryUsageGaugeSet gauges = new MemoryUsageGaugeSet(mxBean,
- Arrays.asList(memoryPool,
- weirdMemoryPool));
+ Arrays.asList(memoryPool,
+ weirdMemoryPool));
@Before
- public void setUp() throws Exception {
+ public void setUp() {
when(heap.getCommitted()).thenReturn(10L);
when(heap.getInit()).thenReturn(20L);
when(heap.getUsed()).thenReturn(30L);
@@ -66,7 +67,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasASetOfGauges() throws Exception {
+ public void hasASetOfGauges() {
assertThat(gauges.getMetrics().keySet())
.containsOnly(
"heap.init",
@@ -98,7 +99,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForTotalCommitted() throws Exception {
+ public void hasAGaugeForTotalCommitted() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("total.committed");
assertThat(gauge.getValue())
@@ -106,7 +107,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForTotalInit() throws Exception {
+ public void hasAGaugeForTotalInit() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("total.init");
assertThat(gauge.getValue())
@@ -114,7 +115,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForTotalUsed() throws Exception {
+ public void hasAGaugeForTotalUsed() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("total.used");
assertThat(gauge.getValue())
@@ -122,7 +123,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForTotalMax() throws Exception {
+ public void hasAGaugeForTotalMax() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("total.max");
assertThat(gauge.getValue())
@@ -130,7 +131,17 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForHeapCommitted() throws Exception {
+ public void hasAGaugeForTotalMaxWhenNonHeapMaxUndefined() {
+ when(nonHeap.getMax()).thenReturn(-1L);
+
+ final Gauge gauge = (Gauge) gauges.getMetrics().get("total.max");
+
+ assertThat(gauge.getValue())
+ .isEqualTo(-1L);
+ }
+
+ @Test
+ public void hasAGaugeForHeapCommitted() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("heap.committed");
assertThat(gauge.getValue())
@@ -138,7 +149,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForHeapInit() throws Exception {
+ public void hasAGaugeForHeapInit() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("heap.init");
assertThat(gauge.getValue())
@@ -146,7 +157,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForHeapUsed() throws Exception {
+ public void hasAGaugeForHeapUsed() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("heap.used");
assertThat(gauge.getValue())
@@ -154,7 +165,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForHeapMax() throws Exception {
+ public void hasAGaugeForHeapMax() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("heap.max");
assertThat(gauge.getValue())
@@ -162,7 +173,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForHeapUsage() throws Exception {
+ public void hasAGaugeForHeapUsage() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("heap.usage");
assertThat(gauge.getValue())
@@ -170,7 +181,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForNonHeapCommitted() throws Exception {
+ public void hasAGaugeForNonHeapCommitted() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("non-heap.committed");
assertThat(gauge.getValue())
@@ -178,7 +189,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForNonHeapInit() throws Exception {
+ public void hasAGaugeForNonHeapInit() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("non-heap.init");
assertThat(gauge.getValue())
@@ -186,7 +197,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForNonHeapUsed() throws Exception {
+ public void hasAGaugeForNonHeapUsed() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("non-heap.used");
assertThat(gauge.getValue())
@@ -194,7 +205,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForNonHeapMax() throws Exception {
+ public void hasAGaugeForNonHeapMax() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("non-heap.max");
assertThat(gauge.getValue())
@@ -202,7 +213,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForNonHeapUsage() throws Exception {
+ public void hasAGaugeForNonHeapUsage() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("non-heap.usage");
assertThat(gauge.getValue())
@@ -210,7 +221,16 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForMemoryPoolUsage() throws Exception {
+ public void hasAGaugeForNonHeapUsageWhenNonHeapMaxUndefined() {
+ when(nonHeap.getMax()).thenReturn(-1L);
+ final Gauge gauge = (Gauge) gauges.getMetrics().get("non-heap.usage");
+
+ assertThat(gauge.getValue())
+ .isEqualTo(3.0);
+ }
+
+ @Test
+ public void hasAGaugeForMemoryPoolUsage() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("pools.Big-Pool.usage");
assertThat(gauge.getValue())
@@ -218,7 +238,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForWeirdMemoryPoolInit() throws Exception {
+ public void hasAGaugeForWeirdMemoryPoolInit() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("pools.Weird-Pool.init");
assertThat(gauge.getValue())
@@ -226,7 +246,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForWeirdMemoryPoolCommitted() throws Exception {
+ public void hasAGaugeForWeirdMemoryPoolCommitted() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("pools.Weird-Pool.committed");
assertThat(gauge.getValue())
@@ -234,7 +254,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForWeirdMemoryPoolUsed() throws Exception {
+ public void hasAGaugeForWeirdMemoryPoolUsed() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("pools.Weird-Pool.used");
assertThat(gauge.getValue())
@@ -242,7 +262,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForWeirdMemoryPoolUsage() throws Exception {
+ public void hasAGaugeForWeirdMemoryPoolUsage() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("pools.Weird-Pool.usage");
assertThat(gauge.getValue())
@@ -250,7 +270,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForWeirdMemoryPoolMax() throws Exception {
+ public void hasAGaugeForWeirdMemoryPoolMax() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("pools.Weird-Pool.max");
assertThat(gauge.getValue())
@@ -258,7 +278,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void hasAGaugeForWeirdCollectionPoolUsed() throws Exception {
+ public void hasAGaugeForWeirdCollectionPoolUsed() {
final Gauge gauge = (Gauge) gauges.getMetrics().get("pools.Weird-Pool.used-after-gc");
assertThat(gauge.getValue())
@@ -266,7 +286,7 @@ public class MemoryUsageGaugeSetTest {
}
@Test
- public void autoDetectsMemoryUsageBeanAndMemoryPools() throws Exception {
+ public void autoDetectsMemoryUsageBeanAndMemoryPools() {
assertThat(new MemoryUsageGaugeSet().getMetrics().keySet())
.isNotEmpty();
}
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDeadlockDetectorTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDeadlockDetectorTest.java
index 2070f37..fd128a0 100644
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDeadlockDetectorTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDeadlockDetectorTest.java
@@ -7,8 +7,8 @@ import java.lang.management.ThreadMXBean;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -17,22 +17,22 @@ public class ThreadDeadlockDetectorTest {
private final ThreadDeadlockDetector detector = new ThreadDeadlockDetector(threads);
@Test
- public void returnsAnEmptySetIfNoThreadsAreDeadlocked() throws Exception {
+ public void returnsAnEmptySetIfNoThreadsAreDeadlocked() {
when(threads.findDeadlockedThreads()).thenReturn(null);
assertThat(detector.getDeadlockedThreads())
- .isEmpty();
+ .isEmpty();
}
@Test
- public void returnsASetOfThreadsIfAnyAreDeadlocked() throws Exception {
+ public void returnsASetOfThreadsIfAnyAreDeadlocked() {
final ThreadInfo thread1 = mock(ThreadInfo.class);
when(thread1.getThreadName()).thenReturn("thread1");
when(thread1.getLockName()).thenReturn("lock2");
when(thread1.getLockOwnerName()).thenReturn("thread2");
when(thread1.getStackTrace()).thenReturn(new StackTraceElement[]{
- new StackTraceElement("Blah", "bloo", "Blah.java", 150),
- new StackTraceElement("Blah", "blee", "Blah.java", 100)
+ new StackTraceElement("Blah", "bloo", "Blah.java", 150),
+ new StackTraceElement("Blah", "blee", "Blah.java", 100)
});
final ThreadInfo thread2 = mock(ThreadInfo.class);
@@ -40,29 +40,29 @@ public class ThreadDeadlockDetectorTest {
when(thread2.getLockName()).thenReturn("lock1");
when(thread2.getLockOwnerName()).thenReturn("thread1");
when(thread2.getStackTrace()).thenReturn(new StackTraceElement[]{
- new StackTraceElement("Blah", "blee", "Blah.java", 100),
- new StackTraceElement("Blah", "bloo", "Blah.java", 150)
+ new StackTraceElement("Blah", "blee", "Blah.java", 100),
+ new StackTraceElement("Blah", "bloo", "Blah.java", 150)
});
- final long[] ids = { 1, 2 };
+ final long[] ids = {1, 2};
when(threads.findDeadlockedThreads()).thenReturn(ids);
when(threads.getThreadInfo(eq(ids), anyInt()))
- .thenReturn(new ThreadInfo[]{ thread1, thread2 });
+ .thenReturn(new ThreadInfo[]{thread1, thread2});
assertThat(detector.getDeadlockedThreads())
- .containsOnly(String.format(Locale.US,
- "thread1 locked on lock2 (owned by thread2):%n" +
- "\t at Blah.bloo(Blah.java:150)%n" +
- "\t at Blah.blee(Blah.java:100)%n"),
- String.format(Locale.US,
- "thread2 locked on lock1 (owned by thread1):%n" +
- "\t at Blah.blee(Blah.java:100)%n" +
- "\t at Blah.bloo(Blah.java:150)%n"));
+ .containsOnly(String.format(Locale.US,
+ "thread1 locked on lock2 (owned by thread2):%n" +
+ "\t at Blah.bloo(Blah.java:150)%n" +
+ "\t at Blah.blee(Blah.java:100)%n"),
+ String.format(Locale.US,
+ "thread2 locked on lock1 (owned by thread1):%n" +
+ "\t at Blah.blee(Blah.java:100)%n" +
+ "\t at Blah.bloo(Blah.java:150)%n"));
}
@Test
- public void autoDiscoversTheThreadMXBean() throws Exception {
+ public void autoDiscoversTheThreadMXBean() {
assertThat(new ThreadDeadlockDetector().getDeadlockedThreads())
- .isNotNull();
+ .isNotNull();
}
}
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
index a62b6da..d73f98d 100755
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDumpTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDumpTest.java
@@ -22,30 +22,30 @@ public class ThreadDumpTest {
private final ThreadInfo runnable = mock(ThreadInfo.class);
@Before
- public void setUp() throws Exception {
+ public void setUp() {
final StackTraceElement rLine1 = new StackTraceElement("Blah", "blee", "Blah.java", 100);
when(runnable.getThreadName()).thenReturn("runnable");
when(runnable.getThreadId()).thenReturn(100L);
when(runnable.getThreadState()).thenReturn(Thread.State.RUNNABLE);
- when(runnable.getStackTrace()).thenReturn(new StackTraceElement[]{ rLine1 });
- when(runnable.getLockedMonitors()).thenReturn(new MonitorInfo[]{ });
- when(runnable.getLockedSynchronizers()).thenReturn(new LockInfo[]{ });
+ when(runnable.getStackTrace()).thenReturn(new StackTraceElement[]{rLine1});
+ when(runnable.getLockedMonitors()).thenReturn(new MonitorInfo[]{});
+ when(runnable.getLockedSynchronizers()).thenReturn(new LockInfo[]{});
when(threadMXBean.dumpAllThreads(true, true)).thenReturn(new ThreadInfo[]{
- runnable
+ runnable
});
}
@Test
- public void dumpsAllThreads() throws Exception {
+ public void dumpsAllThreads() {
final ByteArrayOutputStream output = new ByteArrayOutputStream();
threadDump.dump(output);
assertThat(output.toString())
- .isEqualTo(String.format("\"runnable\" id=100 state=RUNNABLE%n" +
- " at Blah.blee(Blah.java:100)%n" +
- "%n" +
- "%n"));
+ .isEqualTo(String.format("\"runnable\" id=100 state=RUNNABLE%n" +
+ " at Blah.blee(Blah.java:100)%n" +
+ "%n" +
+ "%n"));
}
}
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadStatesGaugeSetTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadStatesGaugeSetTest.java
index 0ddd116..df235a7 100644
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadStatesGaugeSetTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadStatesGaugeSetTest.java
@@ -17,7 +17,7 @@ public class ThreadStatesGaugeSetTest {
private final ThreadMXBean threads = mock(ThreadMXBean.class);
private final ThreadDeadlockDetector detector = mock(ThreadDeadlockDetector.class);
private final ThreadStatesGaugeSet gauges = new ThreadStatesGaugeSet(threads, detector);
- private final long[] ids = new long[]{ 1, 2, 3 };
+ private final long[] ids = new long[]{1, 2, 3};
private final ThreadInfo newThread = mock(ThreadInfo.class);
private final ThreadInfo runnableThread = mock(ThreadInfo.class);
@@ -26,10 +26,10 @@ public class ThreadStatesGaugeSetTest {
private final ThreadInfo timedWaitingThread = mock(ThreadInfo.class);
private final ThreadInfo terminatedThread = mock(ThreadInfo.class);
- private final Set<String> deadlocks = new HashSet<String>();
+ private final Set<String> deadlocks = new HashSet<>();
@Before
- public void setUp() throws Exception {
+ public void setUp() {
deadlocks.add("yay");
when(newThread.getThreadState()).thenReturn(Thread.State.NEW);
@@ -41,82 +41,98 @@ public class ThreadStatesGaugeSetTest {
when(threads.getAllThreadIds()).thenReturn(ids);
when(threads.getThreadInfo(ids, 0)).thenReturn(new ThreadInfo[]{
- newThread, runnableThread, blockedThread,
- waitingThread, timedWaitingThread, terminatedThread
+ newThread, runnableThread, blockedThread,
+ waitingThread, timedWaitingThread, terminatedThread
});
when(threads.getThreadCount()).thenReturn(12);
- when(threads.getDaemonThreadCount()).thenReturn(13);
+ when(threads.getDaemonThreadCount()).thenReturn(10);
+ when(threads.getPeakThreadCount()).thenReturn(30);
+ when(threads.getTotalStartedThreadCount()).thenReturn(42L);
when(detector.getDeadlockedThreads()).thenReturn(deadlocks);
}
@Test
- public void hasASetOfGauges() throws Exception {
+ public void hasASetOfGauges() {
assertThat(gauges.getMetrics().keySet())
- .containsOnly("terminated.count",
- "new.count",
- "count",
- "timed_waiting.count",
- "deadlocks",
- "blocked.count",
- "waiting.count",
- "daemon.count",
- "runnable.count",
- "deadlock.count");
+ .containsOnly("terminated.count",
+ "new.count",
+ "count",
+ "timed_waiting.count",
+ "deadlocks",
+ "blocked.count",
+ "waiting.count",
+ "daemon.count",
+ "runnable.count",
+ "deadlock.count",
+ "total_started.count",
+ "peak.count");
}
@Test
- public void hasAGaugeForEachThreadState() throws Exception {
- assertThat(((Gauge) gauges.getMetrics().get("new.count")).getValue())
- .isEqualTo(1);
+ public void hasAGaugeForEachThreadState() {
+ assertThat(((Gauge<?>) gauges.getMetrics().get("new.count")).getValue())
+ .isEqualTo(1);
- assertThat(((Gauge) gauges.getMetrics().get("runnable.count")).getValue())
- .isEqualTo(1);
+ assertThat(((Gauge<?>) gauges.getMetrics().get("runnable.count")).getValue())
+ .isEqualTo(1);
- assertThat(((Gauge) gauges.getMetrics().get("blocked.count")).getValue())
- .isEqualTo(1);
+ assertThat(((Gauge<?>) gauges.getMetrics().get("blocked.count")).getValue())
+ .isEqualTo(1);
- assertThat(((Gauge) gauges.getMetrics().get("waiting.count")).getValue())
- .isEqualTo(1);
+ assertThat(((Gauge<?>) gauges.getMetrics().get("waiting.count")).getValue())
+ .isEqualTo(1);
- assertThat(((Gauge) gauges.getMetrics().get("timed_waiting.count")).getValue())
- .isEqualTo(1);
+ assertThat(((Gauge<?>) gauges.getMetrics().get("timed_waiting.count")).getValue())
+ .isEqualTo(1);
- assertThat(((Gauge) gauges.getMetrics().get("terminated.count")).getValue())
- .isEqualTo(1);
+ assertThat(((Gauge<?>) gauges.getMetrics().get("terminated.count")).getValue())
+ .isEqualTo(1);
}
@Test
- public void hasAGaugeForTheNumberOfThreads() throws Exception {
- assertThat(((Gauge) gauges.getMetrics().get("count")).getValue())
- .isEqualTo(12);
+ public void hasAGaugeForTheNumberOfThreads() {
+ assertThat(((Gauge<?>) gauges.getMetrics().get("count")).getValue())
+ .isEqualTo(12);
}
@Test
- public void hasAGaugeForTheNumberOfDaemonThreads() throws Exception {
- assertThat(((Gauge) gauges.getMetrics().get("daemon.count")).getValue())
- .isEqualTo(13);
+ public void hasAGaugeForTheNumberOfDaemonThreads() {
+ assertThat(((Gauge<?>) gauges.getMetrics().get("daemon.count")).getValue())
+ .isEqualTo(10);
}
@Test
- public void hasAGaugeForAnyDeadlocks() throws Exception {
- assertThat(((Gauge) gauges.getMetrics().get("deadlocks")).getValue())
- .isEqualTo(deadlocks);
+ public void hasAGaugeForAnyDeadlocks() {
+ assertThat(((Gauge<?>) gauges.getMetrics().get("deadlocks")).getValue())
+ .isEqualTo(deadlocks);
}
@Test
- public void hasAGaugeForAnyDeadlockCount() throws Exception {
- assertThat(((Gauge) gauges.getMetrics().get("deadlock.count")).getValue())
- .isEqualTo(1);
+ public void hasAGaugeForAnyDeadlockCount() {
+ assertThat(((Gauge<?>) gauges.getMetrics().get("deadlock.count")).getValue())
+ .isEqualTo(1);
}
@Test
- public void autoDiscoversTheMXBeans() throws Exception {
+ public void hasAGaugeForPeakThreadCount() {
+ assertThat(((Gauge<?>) gauges.getMetrics().get("peak.count")).getValue())
+ .isEqualTo(30);
+ }
+
+ @Test
+ public void hasAGaugeForTotalStartedThreadsCount() {
+ assertThat(((Gauge<?>) gauges.getMetrics().get("total_started.count")).getValue())
+ .isEqualTo(42L);
+ }
+
+ @Test
+ public void autoDiscoversTheMXBeans() {
final ThreadStatesGaugeSet set = new ThreadStatesGaugeSet();
- assertThat(((Gauge) set.getMetrics().get("count")).getValue())
- .isNotNull();
- assertThat(((Gauge) set.getMetrics().get("deadlocks")).getValue())
- .isNotNull();
+ assertThat(((Gauge<?>) set.getMetrics().get("count")).getValue())
+ .isNotNull();
+ assertThat(((Gauge<?>) set.getMetrics().get("deadlocks")).getValue())
+ .isNotNull();
}
}
diff --git a/metrics-log4j/pom.xml b/metrics-log4j/pom.xml
deleted file mode 100644
index a008fea..0000000
--- a/metrics-log4j/pom.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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.6</version>
- </parent>
-
- <artifactId>metrics-log4j</artifactId>
- <name>Metrics Integration for Log4j 1.x</name>
- <packaging>bundle</packaging>
- <description>
- An instrumented appender for Log4j 1.x.
- </description>
-
- <dependencies>
- <dependency>
- <groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>1.2.17</version>
- </dependency>
- </dependencies>
-</project>
diff --git a/metrics-log4j2/pom.xml b/metrics-log4j2/pom.xml
index 0b975ed..24a1313 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-log4j2</artifactId>
@@ -16,7 +16,8 @@
</description>
<properties>
- <log4j2.version>2.3</log4j2.version>
+ <javaModuleName>com.codahale.metrics.log4j2</javaModuleName>
+ <log4j2.version>2.22.1</log4j2.version>
</properties>
<build>
@@ -32,21 +33,61 @@
</plugins>
</build>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
- <version>${log4j2.version}</version>
+ <version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
- <version>${log4j2.version}</version>
+ <version>${log4j2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
</dependency>
</dependencies>
</project>
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 78f4af7..808e542 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
@@ -24,8 +24,6 @@ import static com.codahale.metrics.MetricRegistry.name;
@Plugin(name = "MetricsAppender", category = "Core", elementType = "appender")
public class InstrumentedAppender extends AbstractAppender {
- private static final long serialVersionUID = 1L;
-
private transient final MetricRegistry registry;
private transient Meter all;
@@ -39,12 +37,11 @@ public class InstrumentedAppender extends AbstractAppender {
/**
* Create a new instrumented appender using the given registry name.
*
- * @param registryName the name of the registry in {@link SharedMetricRegistries}
- * @param filter The Filter to associate with the Appender.
- * @param layout The layout to use to format the event.
+ * @param registryName the name of the registry in {@link SharedMetricRegistries}
+ * @param filter The Filter to associate with the Appender.
+ * @param layout The layout to use to format the event.
* @param ignoreExceptions If true, exceptions will be logged and suppressed. If false errors will be
- * logged and then passed to the application.
- *
+ * logged and then passed to the application.
*/
public InstrumentedAppender(String registryName, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions) {
this(SharedMetricRegistries.getOrCreate(registryName), filter, layout, ignoreExceptions);
@@ -71,11 +68,11 @@ public class InstrumentedAppender extends AbstractAppender {
/**
* Create a new instrumented appender using the given registry.
*
- * @param registry the metric registry
- * @param filter The Filter to associate with the Appender.
- * @param layout The layout to use to format the event.
+ * @param registry the metric registry
+ * @param filter The Filter to associate with the Appender.
+ * @param layout The layout to use to format the event.
* @param ignoreExceptions If true, exceptions will be logged and suppressed. If false errors will be
- * logged and then passed to the application.
+ * logged and then passed to the application.
*/
public InstrumentedAppender(MetricRegistry registry, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions) {
super(name(Appender.class), filter, layout, ignoreExceptions);
@@ -84,8 +81,9 @@ public class InstrumentedAppender extends AbstractAppender {
/**
* Create a new instrumented appender using the given appender name and registry.
+ *
* @param appenderName The name of the appender.
- * @param registry the metric registry
+ * @param registry the metric registry
*/
public InstrumentedAppender(String appenderName, MetricRegistry registry) {
super(appenderName, null, null, true);
@@ -95,7 +93,7 @@ public class InstrumentedAppender extends AbstractAppender {
@PluginFactory
public static InstrumentedAppender createAppender(
@PluginAttribute("name") String name,
- @PluginAttribute( value = "registryName", defaultString = "log4j2Metrics") String registry) {
+ @PluginAttribute(value = "registryName", defaultString = "log4j2Metrics") String registry) {
return new InstrumentedAppender(name, SharedMetricRegistries.getOrCreate(registry));
}
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
index b17d9ed..ea546b0 100644
--- a/metrics-log4j2/src/test/java/com/codahale/metrics/log4j2/InstrumentedAppenderConfigTest.java
+++ b/metrics-log4j2/src/test/java/com/codahale/metrics/log4j2/InstrumentedAppenderConfigTest.java
@@ -13,53 +13,54 @@ 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";
+ 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;
+ 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();
- }
+ @Before
+ public void setUp() throws Exception {
+ source = new ConfigurationSource(this.getClass().getClassLoader().getResourceAsStream("log4j2-testconfig.xml"));
+ context = Configurator.initialize(null, source);
+ }
- // The biggest test is that we can initialize the log4j2 config at all.
+ @After
+ public void tearDown() {
+ context.stop();
+ }
- @Test
- public void canRecordAll() throws Exception {
- Logger logger = context.getLogger(this.getClass().getName());
+ // The biggest test is that we can initialize the log4j2 config at all.
- 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 canRecordAll() {
+ Logger logger = context.getLogger(this.getClass().getName());
- @Test
- public void canRecordError() 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);
+ }
- 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 canRecordError() {
+ Logger logger = context.getLogger(this.getClass().getName());
- @Test
- public void noInvalidRecording() 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);
+ }
- long initialInfoCount = registry.meter(METRIC_NAME_PREFIX + ".info").getCount();
- logger.error("an error message");
- assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
- .isEqualTo(initialInfoCount);
- }
+ @Test
+ public void noInvalidRecording() {
+ 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/java/com/codahale/metrics/log4j2/InstrumentedAppenderTest.java b/metrics-log4j2/src/test/java/com/codahale/metrics/log4j2/InstrumentedAppenderTest.java
index c32cc6c..ee617c3 100644
--- a/metrics-log4j2/src/test/java/com/codahale/metrics/log4j2/InstrumentedAppenderTest.java
+++ b/metrics-log4j2/src/test/java/com/codahale/metrics/log4j2/InstrumentedAppenderTest.java
@@ -2,7 +2,6 @@ package com.codahale.metrics.log4j2;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
-import com.codahale.metrics.log4j2.InstrumentedAppender;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
@@ -23,17 +22,17 @@ public class InstrumentedAppenderTest {
private final LogEvent event = mock(LogEvent.class);
@Before
- public void setUp() throws Exception {
+ public void setUp() {
appender.start();
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
SharedMetricRegistries.clear();
}
@Test
- public void metersTraceEvents() throws Exception {
+ public void metersTraceEvents() {
when(event.getLevel()).thenReturn(Level.TRACE);
appender.append(event);
@@ -46,7 +45,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersDebugEvents() throws Exception {
+ public void metersDebugEvents() {
when(event.getLevel()).thenReturn(Level.DEBUG);
appender.append(event);
@@ -59,7 +58,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersInfoEvents() throws Exception {
+ public void metersInfoEvents() {
when(event.getLevel()).thenReturn(Level.INFO);
appender.append(event);
@@ -72,7 +71,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersWarnEvents() throws Exception {
+ public void metersWarnEvents() {
when(event.getLevel()).thenReturn(Level.WARN);
appender.append(event);
@@ -85,7 +84,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersErrorEvents() throws Exception {
+ public void metersErrorEvents() {
when(event.getLevel()).thenReturn(Level.ERROR);
appender.append(event);
@@ -98,7 +97,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersFatalEvents() throws Exception {
+ public void metersFatalEvents() {
when(event.getLevel()).thenReturn(Level.FATAL);
appender.append(event);
@@ -111,7 +110,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void usesSharedRegistries() throws Exception {
+ public void usesSharedRegistries() {
String registryName = "registry";
diff --git a/metrics-log4j2/src/test/resources/log4j2-testconfig.xml b/metrics-log4j2/src/test/resources/log4j2-testconfig.xml
index 676eca4..aaa2aa2 100644
--- a/metrics-log4j2/src/test/resources/log4j2-testconfig.xml
+++ b/metrics-log4j2/src/test/resources/log4j2-testconfig.xml
@@ -5,7 +5,7 @@
</Appenders>
<Loggers>
<Root level="INFO">
- <AppenderRef ref="metrics" />
+ <AppenderRef ref="metrics"/>
</Root>
</Loggers>
</Configuration>
diff --git a/metrics-logback/pom.xml b/metrics-logback/pom.xml
index d8a26f0..a86eb93 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-logback</artifactId>
@@ -16,19 +16,71 @@
</description>
<properties>
- <logback.version>1.1.10</logback.version>
+ <javaModuleName>com.codahale.metrics.logback</javaModuleName>
+ <logback.version>1.2.13</logback.version>
</properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
</dependency>
</dependencies>
</project>
diff --git a/metrics-logback/src/main/java/com/codahale/metrics/logback/InstrumentedAppender.java b/metrics-logback/src/main/java/com/codahale/metrics/logback/InstrumentedAppender.java
index 2b5f5f1..9293fe3 100644
--- a/metrics-logback/src/main/java/com/codahale/metrics/logback/InstrumentedAppender.java
+++ b/metrics-logback/src/main/java/com/codahale/metrics/logback/InstrumentedAppender.java
@@ -30,11 +30,11 @@ public class InstrumentedAppender extends UnsynchronizedAppenderBase<ILoggingEve
/**
* Create a new instrumented appender using the given registry name.
- *
*/
public InstrumentedAppender() {
- this(System.getProperty(REGISTRY_PROPERTY_NAME, DEFAULT_REGISTRY));
+ this(System.getProperty(REGISTRY_PROPERTY_NAME, DEFAULT_REGISTRY));
}
+
/**
* Create a new instrumented appender using the given registry name.
*
diff --git a/metrics-logback/src/test/java/com/codahale/metrics/logback/InstrumentedAppenderTest.java b/metrics-logback/src/test/java/com/codahale/metrics/logback/InstrumentedAppenderTest.java
index 71c81f4..2b46e2a 100644
--- a/metrics-logback/src/test/java/com/codahale/metrics/logback/InstrumentedAppenderTest.java
+++ b/metrics-logback/src/test/java/com/codahale/metrics/logback/InstrumentedAppenderTest.java
@@ -22,17 +22,17 @@ public class InstrumentedAppenderTest {
private final ILoggingEvent event = mock(ILoggingEvent.class);
@Before
- public void setUp() throws Exception {
+ public void setUp() {
appender.start();
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
SharedMetricRegistries.clear();
}
@Test
- public void metersTraceEvents() throws Exception {
+ public void metersTraceEvents() {
when(event.getLevel()).thenReturn(Level.TRACE);
appender.doAppend(event);
@@ -45,7 +45,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersDebugEvents() throws Exception {
+ public void metersDebugEvents() {
when(event.getLevel()).thenReturn(Level.DEBUG);
appender.doAppend(event);
@@ -58,7 +58,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersInfoEvents() throws Exception {
+ public void metersInfoEvents() {
when(event.getLevel()).thenReturn(Level.INFO);
appender.doAppend(event);
@@ -71,7 +71,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersWarnEvents() throws Exception {
+ public void metersWarnEvents() {
when(event.getLevel()).thenReturn(Level.WARN);
appender.doAppend(event);
@@ -84,7 +84,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersErrorEvents() throws Exception {
+ public void metersErrorEvents() {
when(event.getLevel()).thenReturn(Level.ERROR);
appender.doAppend(event);
@@ -97,7 +97,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void usesSharedRegistries() throws Exception {
+ public void usesSharedRegistries() {
String registryName = "registry";
@@ -114,30 +114,30 @@ public class InstrumentedAppenderTest {
}
@Test
- public void usesDefaultRegistry() throws Exception {
- SharedMetricRegistries.add(InstrumentedAppender.DEFAULT_REGISTRY, registry);
- final InstrumentedAppender shared = new InstrumentedAppender();
- shared.start();
- when(event.getLevel()).thenReturn(Level.INFO);
- shared.doAppend(event);
-
- assertThat(SharedMetricRegistries.names().contains(InstrumentedAppender.DEFAULT_REGISTRY));
- assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
- .isEqualTo(1);
+ public void usesDefaultRegistry() {
+ SharedMetricRegistries.add(InstrumentedAppender.DEFAULT_REGISTRY, registry);
+ final InstrumentedAppender shared = new InstrumentedAppender();
+ shared.start();
+ when(event.getLevel()).thenReturn(Level.INFO);
+ shared.doAppend(event);
+
+ assertThat(SharedMetricRegistries.names()).contains(InstrumentedAppender.DEFAULT_REGISTRY);
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
+ .isEqualTo(1);
}
@Test
- public void usesRegistryFromProperty() throws Exception {
- SharedMetricRegistries.add("something_else", registry);
- System.setProperty(InstrumentedAppender.REGISTRY_PROPERTY_NAME, "something_else");
- final InstrumentedAppender shared = new InstrumentedAppender();
- shared.start();
- when(event.getLevel()).thenReturn(Level.INFO);
- shared.doAppend(event);
-
- assertThat(SharedMetricRegistries.names().contains("something_else"));
- assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
- .isEqualTo(1);
+ public void usesRegistryFromProperty() {
+ SharedMetricRegistries.add("something_else", registry);
+ System.setProperty(InstrumentedAppender.REGISTRY_PROPERTY_NAME, "something_else");
+ final InstrumentedAppender shared = new InstrumentedAppender();
+ shared.start();
+ when(event.getLevel()).thenReturn(Level.INFO);
+ shared.doAppend(event);
+
+ assertThat(SharedMetricRegistries.names()).contains("something_else");
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
+ .isEqualTo(1);
}
}
diff --git a/metrics-logback13/pom.xml b/metrics-logback13/pom.xml
new file mode 100644
index 0000000..84b3f7b
--- /dev/null
+++ b/metrics-logback13/pom.xml
@@ -0,0 +1,93 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-logback13</artifactId>
+ <name>Metrics Integration for Logback 1.3.x</name>
+ <packaging>bundle</packaging>
+ <description>
+ An instrumented appender for Logback 1.3.x.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.logback13</javaModuleName>
+ <logback13.version>1.3.14</logback13.version>
+ <slf4j.version>2.0.11</slf4j.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>${logback13.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>${logback13.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-log4j/src/main/java/com/codahale/metrics/log4j/InstrumentedAppender.java b/metrics-logback13/src/main/java/io/dropwizard/metrics/logback13/InstrumentedAppender.java
similarity index 70%
rename from metrics-log4j/src/main/java/com/codahale/metrics/log4j/InstrumentedAppender.java
rename to metrics-logback13/src/main/java/io/dropwizard/metrics/logback13/InstrumentedAppender.java
index 4d424a8..ab23fe3 100644
--- a/metrics-log4j/src/main/java/com/codahale/metrics/log4j/InstrumentedAppender.java
+++ b/metrics-logback13/src/main/java/io/dropwizard/metrics/logback13/InstrumentedAppender.java
@@ -1,22 +1,24 @@
-package com.codahale.metrics.log4j;
+package io.dropwizard.metrics.logback13;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.UnsynchronizedAppenderBase;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
-import org.apache.log4j.Appender;
-import org.apache.log4j.AppenderSkeleton;
-import org.apache.log4j.Level;
-import org.apache.log4j.spi.LoggingEvent;
import static com.codahale.metrics.MetricRegistry.name;
/**
- * A Log4J {@link Appender} which has seven meters, one for each logging level and one for the total
+ * A Logback {@link Appender} which has six meters, one for each logging level and one for the total
* number of statements being logged. The meter names are the logging level names appended to the
* name of the appender.
*/
-public class InstrumentedAppender extends AppenderSkeleton {
+public class InstrumentedAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
private final MetricRegistry registry;
+ public static final String DEFAULT_REGISTRY = "logback-metrics";
+ public static final String REGISTRY_PROPERTY_NAME = "metrics.logback.registry";
private Meter all;
private Meter trace;
@@ -24,7 +26,14 @@ public class InstrumentedAppender extends AppenderSkeleton {
private Meter info;
private Meter warn;
private Meter error;
- private Meter fatal;
+
+
+ /**
+ * Create a new instrumented appender using the given registry name.
+ */
+ public InstrumentedAppender() {
+ this(System.getProperty(REGISTRY_PROPERTY_NAME, DEFAULT_REGISTRY));
+ }
/**
* Create a new instrumented appender using the given registry name.
@@ -42,22 +51,22 @@ public class InstrumentedAppender extends AppenderSkeleton {
*/
public InstrumentedAppender(MetricRegistry registry) {
this.registry = registry;
- setName(name(Appender.class));
+ setName(Appender.class.getName());
}
@Override
- public void activateOptions() {
+ public void start() {
this.all = registry.meter(name(getName(), "all"));
this.trace = registry.meter(name(getName(), "trace"));
this.debug = registry.meter(name(getName(), "debug"));
this.info = registry.meter(name(getName(), "info"));
this.warn = registry.meter(name(getName(), "warn"));
this.error = registry.meter(name(getName(), "error"));
- this.fatal = registry.meter(name(getName(), "fatal"));
+ super.start();
}
@Override
- protected void append(LoggingEvent event) {
+ protected void append(ILoggingEvent event) {
all.mark();
switch (event.getLevel().toInt()) {
case Level.TRACE_INT:
@@ -75,21 +84,8 @@ public class InstrumentedAppender extends AppenderSkeleton {
case Level.ERROR_INT:
error.mark();
break;
- case Level.FATAL_INT:
- fatal.mark();
- break;
default:
break;
}
}
-
- @Override
- public void close() {
- // nothing doing
- }
-
- @Override
- public boolean requiresLayout() {
- return false;
- }
}
diff --git a/metrics-log4j/src/test/java/com/codahale/metrics/log4j/InstrumentedAppenderTest.java b/metrics-logback13/src/test/java/io/dropwizard/metrics/logback13/InstrumentedAppenderTest.java
similarity index 61%
rename from metrics-log4j/src/test/java/com/codahale/metrics/log4j/InstrumentedAppenderTest.java
rename to metrics-logback13/src/test/java/io/dropwizard/metrics/logback13/InstrumentedAppenderTest.java
index 2550fe5..9c3600d 100644
--- a/metrics-log4j/src/test/java/com/codahale/metrics/log4j/InstrumentedAppenderTest.java
+++ b/metrics-logback13/src/test/java/io/dropwizard/metrics/logback13/InstrumentedAppenderTest.java
@@ -1,9 +1,9 @@
-package com.codahale.metrics.log4j;
+package io.dropwizard.metrics.logback13;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
-import org.apache.log4j.Level;
-import org.apache.log4j.spi.LoggingEvent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -14,24 +14,24 @@ import static org.mockito.Mockito.when;
public class InstrumentedAppenderTest {
- public static final String METRIC_NAME_PREFIX = "org.apache.log4j.Appender";
+ public static final String METRIC_NAME_PREFIX = "ch.qos.logback.core.Appender";
private final MetricRegistry registry = new MetricRegistry();
private final InstrumentedAppender appender = new InstrumentedAppender(registry);
- private final LoggingEvent event = mock(LoggingEvent.class);
+ private final ILoggingEvent event = mock(ILoggingEvent.class);
@Before
- public void setUp() throws Exception {
- appender.activateOptions();
+ public void setUp() {
+ appender.start();
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
SharedMetricRegistries.clear();
}
@Test
- public void metersTraceEvents() throws Exception {
+ public void metersTraceEvents() {
when(event.getLevel()).thenReturn(Level.TRACE);
appender.doAppend(event);
@@ -44,7 +44,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersDebugEvents() throws Exception {
+ public void metersDebugEvents() {
when(event.getLevel()).thenReturn(Level.DEBUG);
appender.doAppend(event);
@@ -57,7 +57,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersInfoEvents() throws Exception {
+ public void metersInfoEvents() {
when(event.getLevel()).thenReturn(Level.INFO);
appender.doAppend(event);
@@ -70,7 +70,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersWarnEvents() throws Exception {
+ public void metersWarnEvents() {
when(event.getLevel()).thenReturn(Level.WARN);
appender.doAppend(event);
@@ -83,7 +83,7 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersErrorEvents() throws Exception {
+ public void metersErrorEvents() {
when(event.getLevel()).thenReturn(Level.ERROR);
appender.doAppend(event);
@@ -96,32 +96,47 @@ public class InstrumentedAppenderTest {
}
@Test
- public void metersFatalEvents() throws Exception {
- when(event.getLevel()).thenReturn(Level.FATAL);
+ public void usesSharedRegistries() {
- appender.doAppend(event);
+ String registryName = "registry";
- assertThat(registry.meter(METRIC_NAME_PREFIX + ".all").getCount())
- .isEqualTo(1);
+ SharedMetricRegistries.add(registryName, registry);
+ final InstrumentedAppender shared = new InstrumentedAppender(registryName);
+ shared.start();
- assertThat(registry.meter(METRIC_NAME_PREFIX + ".fatal").getCount())
+ when(event.getLevel()).thenReturn(Level.INFO);
+
+ shared.doAppend(event);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
.isEqualTo(1);
}
@Test
- public void usesSharedRegistries() throws Exception {
- String registryName = "registry";
-
- SharedMetricRegistries.add(registryName, registry);
+ public void usesDefaultRegistry() {
+ SharedMetricRegistries.add(InstrumentedAppender.DEFAULT_REGISTRY, registry);
+ final InstrumentedAppender shared = new InstrumentedAppender();
+ shared.start();
+ when(event.getLevel()).thenReturn(Level.INFO);
+ shared.doAppend(event);
- final InstrumentedAppender shared = new InstrumentedAppender(registryName);
- shared.activateOptions();
+ assertThat(SharedMetricRegistries.names()).contains(InstrumentedAppender.DEFAULT_REGISTRY);
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
+ .isEqualTo(1);
+ }
+ @Test
+ public void usesRegistryFromProperty() {
+ SharedMetricRegistries.add("something_else", registry);
+ System.setProperty(InstrumentedAppender.REGISTRY_PROPERTY_NAME, "something_else");
+ final InstrumentedAppender shared = new InstrumentedAppender();
+ shared.start();
when(event.getLevel()).thenReturn(Level.INFO);
-
shared.doAppend(event);
+ assertThat(SharedMetricRegistries.names()).contains("something_else");
assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
.isEqualTo(1);
}
+
}
diff --git a/metrics-logback14/pom.xml b/metrics-logback14/pom.xml
new file mode 100644
index 0000000..f64ad8c
--- /dev/null
+++ b/metrics-logback14/pom.xml
@@ -0,0 +1,83 @@
+<?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>4.2.25</version>
+ </parent>
+
+ <artifactId>metrics-logback14</artifactId>
+ <name>Metrics Integration for Logback 1.4.x</name>
+ <packaging>bundle</packaging>
+ <description>
+ An instrumented appender for Logback 1.4.x.
+ </description>
+
+ <properties>
+ <javaModuleName>io.dropwizard.metrics.logback14</javaModuleName>
+ <logback14.version>1.4.14</logback14.version>
+
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>${logback14.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>${logback14.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/metrics-logback14/src/main/java/io/dropwizard/metrics/logback14/InstrumentedAppender.java b/metrics-logback14/src/main/java/io/dropwizard/metrics/logback14/InstrumentedAppender.java
new file mode 100644
index 0000000..e41f95b
--- /dev/null
+++ b/metrics-logback14/src/main/java/io/dropwizard/metrics/logback14/InstrumentedAppender.java
@@ -0,0 +1,91 @@
+package io.dropwizard.metrics.logback14;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.UnsynchronizedAppenderBase;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.SharedMetricRegistries;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+/**
+ * A Logback {@link Appender} which has six meters, one for each logging level and one for the total
+ * number of statements being logged. The meter names are the logging level names appended to the
+ * name of the appender.
+ */
+public class InstrumentedAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
+ private final MetricRegistry registry;
+ public static final String DEFAULT_REGISTRY = "logback-metrics";
+ public static final String REGISTRY_PROPERTY_NAME = "metrics.logback.registry";
+
+ private Meter all;
+ private Meter trace;
+ private Meter debug;
+ private Meter info;
+ private Meter warn;
+ private Meter error;
+
+
+ /**
+ * Create a new instrumented appender using the given registry name.
+ */
+ public InstrumentedAppender() {
+ this(System.getProperty(REGISTRY_PROPERTY_NAME, DEFAULT_REGISTRY));
+ }
+
+ /**
+ * Create a new instrumented appender using the given registry name.
+ *
+ * @param registryName the name of the registry in {@link SharedMetricRegistries}
+ */
+ public InstrumentedAppender(String registryName) {
+ this(SharedMetricRegistries.getOrCreate(registryName));
+ }
+
+ /**
+ * Create a new instrumented appender using the given registry.
+ *
+ * @param registry the metric registry
+ */
+ public InstrumentedAppender(MetricRegistry registry) {
+ this.registry = registry;
+ setName(Appender.class.getName());
+ }
+
+ @Override
+ public void start() {
+ this.all = registry.meter(name(getName(), "all"));
+ this.trace = registry.meter(name(getName(), "trace"));
+ this.debug = registry.meter(name(getName(), "debug"));
+ this.info = registry.meter(name(getName(), "info"));
+ this.warn = registry.meter(name(getName(), "warn"));
+ this.error = registry.meter(name(getName(), "error"));
+ super.start();
+ }
+
+ @Override
+ protected void append(ILoggingEvent event) {
+ all.mark();
+ switch (event.getLevel().toInt()) {
+ case Level.TRACE_INT:
+ trace.mark();
+ break;
+ case Level.DEBUG_INT:
+ debug.mark();
+ break;
+ case Level.INFO_INT:
+ info.mark();
+ break;
+ case Level.WARN_INT:
+ warn.mark();
+ break;
+ case Level.ERROR_INT:
+ error.mark();
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/metrics-logback14/src/test/java/io/dropwizard/metrics/logback14/InstrumentedAppenderTest.java b/metrics-logback14/src/test/java/io/dropwizard/metrics/logback14/InstrumentedAppenderTest.java
new file mode 100644
index 0000000..ce0c570
--- /dev/null
+++ b/metrics-logback14/src/test/java/io/dropwizard/metrics/logback14/InstrumentedAppenderTest.java
@@ -0,0 +1,142 @@
+package io.dropwizard.metrics.logback14;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.SharedMetricRegistries;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class InstrumentedAppenderTest {
+
+ public static final String METRIC_NAME_PREFIX = "ch.qos.logback.core.Appender";
+
+ private final MetricRegistry registry = new MetricRegistry();
+ private final InstrumentedAppender appender = new InstrumentedAppender(registry);
+ private final ILoggingEvent event = mock(ILoggingEvent.class);
+
+ @Before
+ public void setUp() {
+ appender.start();
+ }
+
+ @After
+ public void tearDown() {
+ SharedMetricRegistries.clear();
+ }
+
+ @Test
+ public void metersTraceEvents() {
+ when(event.getLevel()).thenReturn(Level.TRACE);
+
+ appender.doAppend(event);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".all").getCount())
+ .isEqualTo(1);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".trace").getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void metersDebugEvents() {
+ when(event.getLevel()).thenReturn(Level.DEBUG);
+
+ appender.doAppend(event);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".all").getCount())
+ .isEqualTo(1);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".debug").getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void metersInfoEvents() {
+ when(event.getLevel()).thenReturn(Level.INFO);
+
+ appender.doAppend(event);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".all").getCount())
+ .isEqualTo(1);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void metersWarnEvents() {
+ when(event.getLevel()).thenReturn(Level.WARN);
+
+ appender.doAppend(event);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".all").getCount())
+ .isEqualTo(1);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".warn").getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void metersErrorEvents() {
+ when(event.getLevel()).thenReturn(Level.ERROR);
+
+ appender.doAppend(event);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".all").getCount())
+ .isEqualTo(1);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".error").getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void usesSharedRegistries() {
+
+ String registryName = "registry";
+
+ SharedMetricRegistries.add(registryName, registry);
+ final InstrumentedAppender shared = new InstrumentedAppender(registryName);
+ shared.start();
+
+ when(event.getLevel()).thenReturn(Level.INFO);
+
+ shared.doAppend(event);
+
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void usesDefaultRegistry() {
+ SharedMetricRegistries.add(InstrumentedAppender.DEFAULT_REGISTRY, registry);
+ final InstrumentedAppender shared = new InstrumentedAppender();
+ shared.start();
+ when(event.getLevel()).thenReturn(Level.INFO);
+ shared.doAppend(event);
+
+ assertThat(SharedMetricRegistries.names()).contains(InstrumentedAppender.DEFAULT_REGISTRY);
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void usesRegistryFromProperty() {
+ SharedMetricRegistries.add("something_else", registry);
+ System.setProperty(InstrumentedAppender.REGISTRY_PROPERTY_NAME, "something_else");
+ final InstrumentedAppender shared = new InstrumentedAppender();
+ shared.start();
+ when(event.getLevel()).thenReturn(Level.INFO);
+ shared.doAppend(event);
+
+ assertThat(SharedMetricRegistries.names()).contains("something_else");
+ assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
+ .isEqualTo(1);
+ }
+
+}
diff --git a/metrics-servlet/pom.xml b/metrics-servlet/pom.xml
index d5650a3..bca8ac4 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-servlet</artifactId>
@@ -15,11 +15,27 @@
An instrumented filter for servlet environments.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.servlet</javaModuleName>
+ <servlet.version>4.0.1</servlet.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
@@ -27,5 +43,23 @@
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
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 c06dac9..e7bcb37 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
@@ -5,7 +5,14 @@ import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
-import javax.servlet.*;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
@@ -58,26 +65,20 @@ public abstract class AbstractInstrumentedFilter implements Filter {
final MetricRegistry metricsRegistry = getMetricsFactory(filterConfig);
String metricName = filterConfig.getInitParameter(METRIC_PREFIX);
- if(metricName == null || metricName.isEmpty()) {
+ if (metricName == null || metricName.isEmpty()) {
metricName = getClass().getName();
}
- this.metersByStatusCode = new ConcurrentHashMap<Integer, Meter>(meterNamesByStatusCode
- .size());
+ this.metersByStatusCode = new ConcurrentHashMap<>(meterNamesByStatusCode.size());
for (Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) {
metersByStatusCode.put(entry.getKey(),
metricsRegistry.meter(name(metricName, entry.getValue())));
}
- 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,
- "requests"));
+ 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, "requests"));
}
@@ -95,7 +96,7 @@ public abstract class AbstractInstrumentedFilter implements Filter {
@Override
public void destroy() {
-
+
}
@Override
@@ -109,13 +110,7 @@ public abstract class AbstractInstrumentedFilter implements Filter {
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) {
+ } catch (IOException | RuntimeException | ServletException e) {
error = true;
throw e;
} finally {
@@ -169,11 +164,13 @@ public abstract class AbstractInstrumentedFilter implements Filter {
}
@Override
+ @SuppressWarnings("deprecation")
public void setStatus(int sc, String sm) {
httpStatus = sc;
super.setStatus(sc, sm);
}
+ @Override
public int getStatus() {
return httpStatus;
}
diff --git a/metrics-servlet/src/main/java/com/codahale/metrics/servlet/InstrumentedFilter.java b/metrics-servlet/src/main/java/com/codahale/metrics/servlet/InstrumentedFilter.java
index 5333344..f97aa36 100644
--- a/metrics-servlet/src/main/java/com/codahale/metrics/servlet/InstrumentedFilter.java
+++ b/metrics-servlet/src/main/java/com/codahale/metrics/servlet/InstrumentedFilter.java
@@ -5,7 +5,7 @@ import java.util.Map;
/**
* Implementation of the {@link AbstractInstrumentedFilter} which provides a default set of response codes
- * to capture information about. <p>Use it in your servlet.xml like this:</p>
+ * to capture information about. <p>Use it in your servlet.xml like this:<p>
* <pre>{@code
* <filter>
* <filter-name>instrumentedFilter</filter-name>
@@ -36,7 +36,7 @@ public class InstrumentedFilter extends AbstractInstrumentedFilter {
}
private static Map<Integer, String> createMeterNamesByStatusCode() {
- final Map<Integer, String> meterNamesByStatusCode = new HashMap<Integer, String>(6);
+ final Map<Integer, String> meterNamesByStatusCode = new HashMap<>(6);
meterNamesByStatusCode.put(OK, NAME_PREFIX + "ok");
meterNamesByStatusCode.put(CREATED, NAME_PREFIX + "created");
meterNamesByStatusCode.put(NO_CONTENT, NAME_PREFIX + "noContent");
diff --git a/metrics-servlet/src/main/java/com/codahale/metrics/servlet/InstrumentedFilterContextListener.java b/metrics-servlet/src/main/java/com/codahale/metrics/servlet/InstrumentedFilterContextListener.java
index 8cdc01e..3a009ed 100644
--- a/metrics-servlet/src/main/java/com/codahale/metrics/servlet/InstrumentedFilterContextListener.java
+++ b/metrics-servlet/src/main/java/com/codahale/metrics/servlet/InstrumentedFilterContextListener.java
@@ -18,8 +18,7 @@ public abstract class InstrumentedFilterContextListener implements ServletContex
@Override
public void contextInitialized(ServletContextEvent sce) {
- sce.getServletContext().setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE,
- getMetricRegistry());
+ sce.getServletContext().setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE, getMetricRegistry());
}
@Override
diff --git a/metrics-servlet/src/test/java/com/codahale/metrics/servlet/InstrumentedFilterContextListenerTest.java b/metrics-servlet/src/test/java/com/codahale/metrics/servlet/InstrumentedFilterContextListenerTest.java
index 576d889..d94a84a 100644
--- a/metrics-servlet/src/test/java/com/codahale/metrics/servlet/InstrumentedFilterContextListenerTest.java
+++ b/metrics-servlet/src/test/java/com/codahale/metrics/servlet/InstrumentedFilterContextListenerTest.java
@@ -20,7 +20,7 @@ public class InstrumentedFilterContextListenerTest {
};
@Test
- public void injectsTheMetricRegistryIntoTheServletContext() throws Exception {
+ public void injectsTheMetricRegistryIntoTheServletContext() {
final ServletContext context = mock(ServletContext.class);
final ServletContextEvent event = mock(ServletContextEvent.class);
diff --git a/metrics-servlets/pom.xml b/metrics-servlets/pom.xml
index 898c77f..ee64a50 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.2.6</version>
+ <version>4.2.25</version>
</parent>
<artifactId>metrics-servlets</artifactId>
@@ -16,31 +16,58 @@
your production environment.
</description>
+ <properties>
+ <javaModuleName>com.codahale.metrics.servlets</javaModuleName>
+ <papertrail.profiler.version>1.1.1</papertrail.profiler.version>
+ <servlet.version>4.0.1</servlet.version>
+ <jackson.version>2.12.7.1</jackson.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-bom</artifactId>
+ <version>${jetty9.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${byte-buddy.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-healthchecks</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-json</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-jvm</artifactId>
- <version>${project.version}</version>
</dependency>
<dependency>
- <groupId>com.papertrail</groupId>
+ <groupId>com.helger</groupId>
<artifactId>profiler</artifactId>
- <version>1.0.2</version>
+ <version>${papertrail.profiler.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
@@ -53,16 +80,67 @@
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <classifier>tests</classifier>
+ <version>${jetty9.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
+ <classifier>tests</classifier>
<version>${jetty9.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-jetty9</artifactId>
- <version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
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 a3300d4..342c72c 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
@@ -1,6 +1,7 @@
package com.codahale.metrics.servlets;
import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -16,14 +17,19 @@ public class AdminServlet extends HttpServlet {
public static final String DEFAULT_THREADS_URI = "/threads";
public static final String DEFAULT_CPU_PROFILE_URI = "/pprof";
+ public static final String METRICS_ENABLED_PARAM_KEY = "metrics-enabled";
public static final String METRICS_URI_PARAM_KEY = "metrics-uri";
+ public static final String PING_ENABLED_PARAM_KEY = "ping-enabled";
public static final String PING_URI_PARAM_KEY = "ping-uri";
+ public static final String THREADS_ENABLED_PARAM_KEY = "threads-enabled";
public static final String THREADS_URI_PARAM_KEY = "threads-uri";
+ public static final String HEALTHCHECK_ENABLED_PARAM_KEY = "healthcheck-enabled";
public static final String HEALTHCHECK_URI_PARAM_KEY = "healthcheck-uri";
- public static final String SERVICE_NAME_PARAM_KEY= "service-name";
+ public static final String SERVICE_NAME_PARAM_KEY = "service-name";
+ public static final String CPU_PROFILE_ENABLED_PARAM_KEY = "cpu-profile-enabled";
public static final String CPU_PROFILE_URI_PARAM_KEY = "cpu-profile-uri";
- private static final String TEMPLATE = String.format(
+ private static final String BASE_TEMPLATE =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"%n" +
" \"http://www.w3.org/TR/html4/loose.dtd\">%n" +
"<html>%n" +
@@ -33,16 +39,18 @@ public class AdminServlet extends HttpServlet {
"<body>%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" +
+ "%s" +
" </ul>%n" +
"</body>%n" +
- "</html>"
- );
+ "</html>";
+ private static final String METRICS_LINK = " <li><a href=\"{0}{1}?pretty=true\">Metrics</a></li>%n";
+ private static final String PING_LINK = " <li><a href=\"{2}{3}\">Ping</a></li>%n" ;
+ private static final String THREADS_LINK = " <li><a href=\"{4}{5}\">Threads</a></li>%n" ;
+ private static final String HEALTHCHECK_LINK = " <li><a href=\"{6}{7}?pretty=true\">Healthcheck</a></li>%n" ;
+ private static final String CPU_PROFILE_LINK = " <li><a href=\"{8}{9}\">CPU Profile</a></li>%n" +
+ " <li><a href=\"{8}{9}?state=blocked\">CPU Contention</a></li>%n";
+
+
private static final String CONTENT_TYPE = "text/html";
private static final long serialVersionUID = -2850794040708785318L;
@@ -51,38 +59,74 @@ public class AdminServlet extends HttpServlet {
private transient PingServlet pingServlet;
private transient ThreadDumpServlet threadDumpServlet;
private transient CpuProfileServlet cpuProfileServlet;
+ private transient boolean metricsEnabled;
private transient String metricsUri;
+ private transient boolean pingEnabled;
private transient String pingUri;
+ private transient boolean threadsEnabled;
private transient String threadsUri;
+ private transient boolean healthcheckEnabled;
private transient String healthcheckUri;
- private transient String cpuprofileUri;
+ private transient boolean cpuProfileEnabled;
+ private transient String cpuProfileUri;
private transient String serviceName;
+ private transient String pageContentTemplate;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
- this.healthCheckServlet = new HealthCheckServlet();
- healthCheckServlet.init(config);
+ final ServletContext context = config.getServletContext();
+ final StringBuilder servletLinks = new StringBuilder();
+ this.metricsEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(METRICS_ENABLED_PARAM_KEY), "true"));
+ if (this.metricsEnabled) {
+ servletLinks.append(METRICS_LINK);
+ }
this.metricsServlet = new MetricsServlet();
metricsServlet.init(config);
+ this.pingEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(PING_ENABLED_PARAM_KEY), "true"));
+ if (this.pingEnabled) {
+ servletLinks.append(PING_LINK);
+ }
this.pingServlet = new PingServlet();
pingServlet.init(config);
+ this.threadsEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(THREADS_ENABLED_PARAM_KEY), "true"));
+ if (this.threadsEnabled) {
+ servletLinks.append(THREADS_LINK);
+ }
this.threadDumpServlet = new ThreadDumpServlet();
threadDumpServlet.init(config);
+ this.healthcheckEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(HEALTHCHECK_ENABLED_PARAM_KEY), "true"));
+ if (this.healthcheckEnabled) {
+ servletLinks.append(HEALTHCHECK_LINK);
+ }
+ this.healthCheckServlet = new HealthCheckServlet();
+ healthCheckServlet.init(config);
+
+ this.cpuProfileEnabled =
+ Boolean.parseBoolean(getParam(context.getInitParameter(CPU_PROFILE_ENABLED_PARAM_KEY), "true"));
+ if (this.cpuProfileEnabled) {
+ servletLinks.append(CPU_PROFILE_LINK);
+ }
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);
+ pageContentTemplate = String.format(BASE_TEMPLATE, String.format(servletLinks.toString()));
+
+ this.metricsUri = getParam(context.getInitParameter(METRICS_URI_PARAM_KEY), DEFAULT_METRICS_URI);
+ this.pingUri = getParam(context.getInitParameter(PING_URI_PARAM_KEY), DEFAULT_PING_URI);
+ this.threadsUri = getParam(context.getInitParameter(THREADS_URI_PARAM_KEY), DEFAULT_THREADS_URI);
+ this.healthcheckUri = getParam(context.getInitParameter(HEALTHCHECK_URI_PARAM_KEY), DEFAULT_HEALTHCHECK_URI);
+ this.cpuProfileUri = getParam(context.getInitParameter(CPU_PROFILE_URI_PARAM_KEY), DEFAULT_CPU_PROFILE_URI);
+ this.serviceName = getParam(context.getInitParameter(SERVICE_NAME_PARAM_KEY), null);
}
@Override
@@ -92,13 +136,10 @@ public class AdminServlet extends HttpServlet {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
resp.setContentType(CONTENT_TYPE);
- final PrintWriter writer = resp.getWriter();
- try {
- writer.println(MessageFormat.format(TEMPLATE, path, metricsUri, path, pingUri, path,
- threadsUri, path, healthcheckUri, path, cpuprofileUri,
- serviceName == null ? "" : " (" + serviceName + ")"));
- } finally {
- writer.close();
+ try (PrintWriter writer = resp.getWriter()) {
+ writer.println(MessageFormat.format(pageContentTemplate, path, metricsUri, path, pingUri, path,
+ threadsUri, path, healthcheckUri, path, cpuProfileUri,
+ serviceName == null ? "" : " (" + serviceName + ")"));
}
}
@@ -108,15 +149,35 @@ public class AdminServlet extends HttpServlet {
if (uri == null || uri.equals("/")) {
super.service(req, resp);
} else if (uri.equals(healthcheckUri)) {
- healthCheckServlet.service(req, resp);
+ if (healthcheckEnabled) {
+ healthCheckServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
} else if (uri.startsWith(metricsUri)) {
- metricsServlet.service(req, resp);
+ if (metricsEnabled) {
+ metricsServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
} else if (uri.equals(pingUri)) {
- pingServlet.service(req, resp);
+ if (pingEnabled) {
+ pingServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
} else if (uri.equals(threadsUri)) {
- threadDumpServlet.service(req, resp);
- } else if (uri.equals(cpuprofileUri)) {
- cpuProfileServlet.service(req, resp);
+ if (threadsEnabled) {
+ threadDumpServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } else if (uri.equals(cpuProfileUri)) {
+ if (cpuProfileEnabled) {
+ cpuProfileServlet.service(req, resp);
+ } else {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
} else {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
diff --git a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/AdminServletContextListener.java b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/AdminServletContextListener.java
deleted file mode 100644
index 6104638..0000000
--- a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/AdminServletContextListener.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.codahale.metrics.servlets;
-
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.health.HealthCheckRegistry;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-import java.util.concurrent.ExecutorService;
-
-/**
- * A listener implementation which injects a {@link MetricRegistry} instance, a
- * {@link HealthCheckRegistry} instance, and an optional {@link ExecutorService} instance into the
- * servlet context as named attributes.
- *
- * @deprecated Use {@link MetricsServlet.ContextListener} and
- * {@link HealthCheckServlet.ContextListener} instead.
- */
-@Deprecated
-public abstract class AdminServletContextListener implements ServletContextListener {
- /**
- * @return the {@link MetricRegistry} to inject into the servlet context.
- */
- protected abstract MetricRegistry getMetricRegistry();
-
- /**
- * @return the {@link HealthCheckRegistry} to inject into the servlet context.
- */
- protected abstract HealthCheckRegistry getHealthCheckRegistry();
-
- /**
- * @return the {@link ExecutorService} to inject into the servlet context, or {@code null} if
- * the health checks should be run in the servlet worker thread.
- */
- protected ExecutorService getExecutorService() {
- // don't use a thread pool by default
- return null;
- }
-
- @Override
- public void contextInitialized(ServletContextEvent servletContextEvent) {
- final ServletContext context = servletContextEvent.getServletContext();
- context.setAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY, getHealthCheckRegistry());
- context.setAttribute(HealthCheckServlet.HEALTH_CHECK_EXECUTOR, getExecutorService());
- context.setAttribute(MetricsServlet.METRICS_REGISTRY, getMetricRegistry());
- }
-
- @Override
- public void contextDestroyed(ServletContextEvent servletContextEvent) {
- // no-op...
- }
-}
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
index a49f9a6..88a9089 100644
--- a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/CpuProfileServlet.java
+++ b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/CpuProfileServlet.java
@@ -2,13 +2,14 @@ package com.codahale.metrics.servlets;
import java.io.IOException;
import java.io.OutputStream;
+import java.time.Duration;
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;
/**
@@ -38,6 +39,7 @@ public class CpuProfileServlet extends HttpServlet {
if (req.getParameter("frequency") != null) {
try {
frequency = Integer.parseInt(req.getParameter("frequency"));
+ frequency = Math.min(Math.max(frequency, 1), 1000);
} catch (NumberFormatException e) {
frequency = 100;
}
@@ -46,26 +48,22 @@ public class CpuProfileServlet extends HttpServlet {
final Thread.State state;
if ("blocked".equalsIgnoreCase(req.getParameter("state"))) {
state = Thread.State.BLOCKED;
- }
- else {
+ } 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 {
+ try (OutputStream output = resp.getOutputStream()) {
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),
+ CpuProfile profile = CpuProfile.record(Duration.ofSeconds(duration),
frequency, state);
if (profile == null) {
throw new RuntimeException("could not create CpuProfile");
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 c670158..3865a2d 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
@@ -1,12 +1,17 @@
package com.codahale.metrics.servlets;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.ObjectWriter;
import com.codahale.metrics.health.HealthCheck;
+import com.codahale.metrics.health.HealthCheckFilter;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.codahale.metrics.json.HealthCheckModule;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
-import javax.servlet.*;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -32,11 +37,29 @@ public class HealthCheckServlet extends HttpServlet {
return null;
}
+ /**
+ * @return the {@link HealthCheckFilter} that shall be used to filter health checks,
+ * or {@link HealthCheckFilter#ALL} if the default should be used.
+ */
+ protected HealthCheckFilter getHealthCheckFilter() {
+ return HealthCheckFilter.ALL;
+ }
+
+ /**
+ * @return the {@link ObjectMapper} that shall be used to render health checks,
+ * or {@code null} if the default object mapper should be used.
+ */
+ protected ObjectMapper getObjectMapper() {
+ // don't use an object mapper by default
+ return null;
+ }
+
@Override
public void contextInitialized(ServletContextEvent event) {
final ServletContext context = event.getServletContext();
context.setAttribute(HEALTH_CHECK_REGISTRY, getHealthCheckRegistry());
context.setAttribute(HEALTH_CHECK_EXECUTOR, getExecutorService());
+ context.setAttribute(HEALTH_CHECK_MAPPER, getObjectMapper());
}
@Override
@@ -47,13 +70,19 @@ public class HealthCheckServlet extends HttpServlet {
public static final String HEALTH_CHECK_REGISTRY = HealthCheckServlet.class.getCanonicalName() + ".registry";
public static final String HEALTH_CHECK_EXECUTOR = HealthCheckServlet.class.getCanonicalName() + ".executor";
+ public static final String HEALTH_CHECK_FILTER = HealthCheckServlet.class.getCanonicalName() + ".healthCheckFilter";
+ public static final String HEALTH_CHECK_MAPPER = HealthCheckServlet.class.getCanonicalName() + ".mapper";
+ public static final String HEALTH_CHECK_HTTP_STATUS_INDICATOR = HealthCheckServlet.class.getCanonicalName() + ".httpStatusIndicator";
private static final long serialVersionUID = -8432996484889177321L;
private static final String CONTENT_TYPE = "application/json";
+ private static final String HTTP_STATUS_INDICATOR_PARAM = "httpStatusIndicator";
private transient HealthCheckRegistry registry;
private transient ExecutorService executorService;
+ private transient HealthCheckFilter filter;
private transient ObjectMapper mapper;
+ private transient boolean httpStatusIndicator;
public HealthCheckServlet() {
}
@@ -61,13 +90,14 @@ public class HealthCheckServlet extends HttpServlet {
public HealthCheckServlet(HealthCheckRegistry registry) {
this.registry = registry;
}
-
+
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
+ final ServletContext context = config.getServletContext();
if (null == registry) {
- final Object registryAttr = config.getServletContext().getAttribute(HEALTH_CHECK_REGISTRY);
+ final Object registryAttr = context.getAttribute(HEALTH_CHECK_REGISTRY);
if (registryAttr instanceof HealthCheckRegistry) {
this.registry = (HealthCheckRegistry) registryAttr;
} else {
@@ -75,12 +105,33 @@ public class HealthCheckServlet extends HttpServlet {
}
}
- final Object executorAttr = config.getServletContext().getAttribute(HEALTH_CHECK_EXECUTOR);
+ final Object executorAttr = context.getAttribute(HEALTH_CHECK_EXECUTOR);
if (executorAttr instanceof ExecutorService) {
this.executorService = (ExecutorService) executorAttr;
}
- this.mapper = new ObjectMapper().registerModule(new HealthCheckModule());
+ final Object filterAttr = context.getAttribute(HEALTH_CHECK_FILTER);
+ if (filterAttr instanceof HealthCheckFilter) {
+ filter = (HealthCheckFilter) filterAttr;
+ }
+ if (filter == null) {
+ filter = HealthCheckFilter.ALL;
+ }
+
+ final Object mapperAttr = context.getAttribute(HEALTH_CHECK_MAPPER);
+ if (mapperAttr instanceof ObjectMapper) {
+ this.mapper = (ObjectMapper) mapperAttr;
+ } else {
+ this.mapper = new ObjectMapper();
+ }
+ this.mapper.registerModule(new HealthCheckModule());
+
+ final Object httpStatusIndicatorAttr = context.getAttribute(HEALTH_CHECK_HTTP_STATUS_INDICATOR);
+ if (httpStatusIndicatorAttr instanceof Boolean) {
+ this.httpStatusIndicator = (Boolean) httpStatusIndicatorAttr;
+ } else {
+ this.httpStatusIndicator = true;
+ }
}
@Override
@@ -98,18 +149,18 @@ public class HealthCheckServlet extends HttpServlet {
if (results.isEmpty()) {
resp.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
} else {
- if (isAllHealthy(results)) {
+ final String reqParameter = req.getParameter(HTTP_STATUS_INDICATOR_PARAM);
+ final boolean httpStatusIndicatorParam = Boolean.parseBoolean(reqParameter);
+ final boolean useHttpStatusForHealthCheck = reqParameter == null ? httpStatusIndicator : httpStatusIndicatorParam;
+ if (!useHttpStatusForHealthCheck || isAllHealthy(results)) {
resp.setStatus(HttpServletResponse.SC_OK);
} else {
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
- final OutputStream output = resp.getOutputStream();
- try {
+ try (OutputStream output = resp.getOutputStream()) {
getWriter(req).writeValue(output, results);
- } finally {
- output.close();
}
}
@@ -123,9 +174,9 @@ public class HealthCheckServlet extends HttpServlet {
private SortedMap<String, HealthCheck.Result> runHealthChecks() {
if (executorService == null) {
- return registry.runHealthChecks();
+ return registry.runHealthChecks(filter);
}
- return registry.runHealthChecks(executorService);
+ return registry.runHealthChecks(executorService, filter);
}
private static boolean isAllHealthy(Map<String, HealthCheck.Result> results) {
@@ -136,4 +187,9 @@ public class HealthCheckServlet extends HttpServlet {
}
return true;
}
+
+ // visible for testing
+ ObjectMapper getMapper() {
+ return mapper;
+ }
}
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 31232cf..0bd1297 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
@@ -114,10 +114,10 @@ public class MetricsServlet extends HttpServlet {
private static final long serialVersionUID = 1049773947734939602L;
private static final String CONTENT_TYPE = "application/json";
- private String allowedOrigin;
- private String jsonpParamName;
- private transient MetricRegistry registry;
- private transient ObjectMapper mapper;
+ protected String allowedOrigin;
+ protected String jsonpParamName;
+ protected transient MetricRegistry registry;
+ protected transient ObjectMapper mapper;
public MetricsServlet() {
}
@@ -139,23 +139,27 @@ public class MetricsServlet extends HttpServlet {
throw new ServletException("Couldn't find a MetricRegistry instance.");
}
}
+ this.allowedOrigin = context.getInitParameter(ALLOWED_ORIGIN);
+ this.jsonpParamName = context.getInitParameter(CALLBACK_PARAM);
+ setupMetricsModule(context);
+ }
+
+ protected void setupMetricsModule(ServletContext context) {
final TimeUnit rateUnit = parseTimeUnit(context.getInitParameter(RATE_UNIT),
- TimeUnit.SECONDS);
+ TimeUnit.SECONDS);
final TimeUnit durationUnit = parseTimeUnit(context.getInitParameter(DURATION_UNIT),
- TimeUnit.SECONDS);
+ TimeUnit.SECONDS);
final boolean showSamples = Boolean.parseBoolean(context.getInitParameter(SHOW_SAMPLES));
MetricFilter filter = (MetricFilter) context.getAttribute(METRIC_FILTER);
if (filter == null) {
- filter = MetricFilter.ALL;
+ filter = MetricFilter.ALL;
}
- this.mapper = new ObjectMapper().registerModule(new MetricsModule(rateUnit,
- durationUnit,
- showSamples,
- filter));
- this.allowedOrigin = context.getInitParameter(ALLOWED_ORIGIN);
- this.jsonpParamName = context.getInitParameter(CALLBACK_PARAM);
+ this.mapper = new ObjectMapper().registerModule(new MetricsModule(rateUnit,
+ durationUnit,
+ showSamples,
+ filter));
}
@Override
@@ -168,19 +172,16 @@ 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 {
+ try (OutputStream output = resp.getOutputStream()) {
if (jsonpParamName != null && req.getParameter(jsonpParamName) != null) {
getWriter(req).writeValue(output, new JSONPObject(req.getParameter(jsonpParamName), registry));
} else {
getWriter(req).writeValue(output, registry);
}
- } finally {
- output.close();
}
}
- private ObjectWriter getWriter(HttpServletRequest request) {
+ protected ObjectWriter getWriter(HttpServletRequest request) {
final boolean prettyPrint = Boolean.parseBoolean(request.getParameter("pretty"));
if (prettyPrint) {
return mapper.writerWithDefaultPrettyPrinter();
@@ -188,7 +189,7 @@ public class MetricsServlet extends HttpServlet {
return mapper.writer();
}
- private TimeUnit parseTimeUnit(String value, TimeUnit defaultValue) {
+ protected TimeUnit parseTimeUnit(String value, TimeUnit defaultValue) {
try {
return TimeUnit.valueOf(String.valueOf(value).toUpperCase(Locale.US));
} catch (IllegalArgumentException e) {
diff --git a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/PingServlet.java b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/PingServlet.java
index 12387ec..6eac5d0 100644
--- a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/PingServlet.java
+++ b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/PingServlet.java
@@ -23,11 +23,8 @@ public class PingServlet extends HttpServlet {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setHeader(CACHE_CONTROL, NO_CACHE);
resp.setContentType(CONTENT_TYPE);
- final PrintWriter writer = resp.getWriter();
- try {
+ try (PrintWriter writer = resp.getWriter()) {
writer.println(CONTENT);
- } finally {
- writer.close();
}
}
}
diff --git a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/ThreadDumpServlet.java b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/ThreadDumpServlet.java
index b1d3e42..80615d1 100644
--- a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/ThreadDumpServlet.java
+++ b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/ThreadDumpServlet.java
@@ -33,7 +33,10 @@ public class ThreadDumpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
- HttpServletResponse resp) throws ServletException, IOException {
+ HttpServletResponse resp) throws ServletException, IOException {
+ final boolean includeMonitors = getParam(req.getParameter("monitors"), true);
+ final boolean includeSynchronizers = getParam(req.getParameter("synchronizers"), true);
+
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType(CONTENT_TYPE);
resp.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
@@ -41,11 +44,12 @@ public class ThreadDumpServlet extends HttpServlet {
resp.getWriter().println("Sorry your runtime environment does not allow to dump threads.");
return;
}
- final OutputStream output = resp.getOutputStream();
- try {
- threadDump.dump(output);
- } finally {
- output.close();
+ try (OutputStream output = resp.getOutputStream()) {
+ threadDump.dump(includeMonitors, includeSynchronizers, output);
}
}
+
+ private static Boolean getParam(String initParam, boolean defaultValue) {
+ return initParam == null ? defaultValue : Boolean.parseBoolean(initParam);
+ }
}
diff --git a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletContextListenerTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletContextListenerTest.java
deleted file mode 100644
index c23a70e..0000000
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletContextListenerTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.codahale.metrics.servlets;
-
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.health.HealthCheckRegistry;
-import org.junit.Before;
-import org.junit.Test;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
-import java.util.concurrent.ExecutorService;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-
-@SuppressWarnings("deprecation")
-public class AdminServletContextListenerTest {
- private final MetricRegistry metricRegistry = mock(MetricRegistry.class);
- private final HealthCheckRegistry healthCheckRegistry = mock(HealthCheckRegistry.class);
- private final ExecutorService executorService = mock(ExecutorService.class);
- private final AdminServletContextListener listener = new AdminServletContextListener() {
- @Override
- protected MetricRegistry getMetricRegistry() {
- return metricRegistry;
- }
-
- @Override
- protected HealthCheckRegistry getHealthCheckRegistry() {
- return healthCheckRegistry;
- }
-
- @Override
- protected ExecutorService getExecutorService() {
- return executorService;
- }
- };
-
- private final ServletContext context = mock(ServletContext.class);
- private final ServletContextEvent event = mock(ServletContextEvent.class);
-
- @Before
- public void setUp() throws Exception {
- when(event.getServletContext()).thenReturn(context);
- }
-
- @Test
- public void injectsTheMetricRegistry() throws Exception {
- listener.contextInitialized(event);
-
- verify(context).setAttribute("com.codahale.metrics.servlets.MetricsServlet.registry", metricRegistry);
- }
-
- @Test
- public void injectsTheHealthCheckRegistry() throws Exception {
- listener.contextInitialized(event);
-
- verify(context).setAttribute("com.codahale.metrics.servlets.HealthCheckServlet.registry", healthCheckRegistry);
- }
-
- @Test
- public void injectsTheHealthCheckExecutor() throws Exception {
- listener.contextInitialized(event);
-
- verify(context).setAttribute("com.codahale.metrics.servlets.HealthCheckServlet.executor", executorService);
- }
-}
diff --git a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletExclusionTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletExclusionTest.java
new file mode 100755
index 0000000..5fafab8
--- /dev/null
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletExclusionTest.java
@@ -0,0 +1,60 @@
+package com.codahale.metrics.servlets;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.health.HealthCheckRegistry;
+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 AdminServletExclusionTest extends AbstractServletTest {
+ private final MetricRegistry registry = new MetricRegistry();
+ private final HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
+
+ @Override
+ protected void setUp(ServletTester tester) {
+ tester.setContextPath("/context");
+
+ tester.setAttribute("com.codahale.metrics.servlets.MetricsServlet.registry", registry);
+ tester.setAttribute("com.codahale.metrics.servlets.HealthCheckServlet.registry", healthCheckRegistry);
+ tester.setInitParameter("threads-enabled", "false");
+ tester.setInitParameter("cpu-profile-enabled", "false");
+ tester.addServlet(AdminServlet.class, "/admin");
+ }
+
+ @Before
+ public void setUp() {
+ request.setMethod("GET");
+ request.setURI("/context/admin");
+ request.setVersion("HTTP/1.0");
+ }
+
+ @Test
+ public void returnsA200() throws Exception {
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.getContent())
+ .isEqualTo(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</title>%n" +
+ "</head>%n" +
+ "<body>%n" +
+ " <h1>Operational Menu</h1>%n" +
+ " <ul>%n" +
+ " <li><a href=\"/context/admin/metrics?pretty=true\">Metrics</a></li>%n" +
+ " <li><a href=\"/context/admin/ping\">Ping</a></li>%n" +
+ " <li><a href=\"/context/admin/healthcheck?pretty=true\">Healthcheck</a></li>%n" +
+ " </ul>%n" +
+ "</body>%n" +
+ "</html>%n"
+ ));
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("text/html;charset=UTF-8");
+ }
+}
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 f6c58f2..4d2e6f8 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
@@ -23,7 +23,7 @@ public class AdminServletTest extends AbstractServletTest {
}
@Before
- public void setUp() throws Exception {
+ public void setUp() {
request.setMethod("GET");
request.setURI("/context/admin");
request.setVersion("HTTP/1.0");
@@ -57,6 +57,6 @@ public class AdminServletTest extends AbstractServletTest {
"</html>%n"
));
assertThat(response.get(HttpHeader.CONTENT_TYPE))
- .isEqualTo("text/html; charset=ISO-8859-1");
+ .isEqualTo("text/html;charset=UTF-8");
}
}
diff --git a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletUriTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletUriTest.java
new file mode 100755
index 0000000..b97530e
--- /dev/null
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletUriTest.java
@@ -0,0 +1,66 @@
+package com.codahale.metrics.servlets;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.health.HealthCheckRegistry;
+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 AdminServletUriTest extends AbstractServletTest {
+ private final MetricRegistry registry = new MetricRegistry();
+ private final HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
+
+ @Override
+ protected void setUp(ServletTester tester) {
+ tester.setContextPath("/context");
+
+ tester.setAttribute("com.codahale.metrics.servlets.MetricsServlet.registry", registry);
+ tester.setAttribute("com.codahale.metrics.servlets.HealthCheckServlet.registry", healthCheckRegistry);
+ tester.setInitParameter("metrics-uri", "/metrics-test");
+ tester.setInitParameter("ping-uri", "/ping-test");
+ tester.setInitParameter("threads-uri", "/threads-test");
+ tester.setInitParameter("healthcheck-uri", "/healthcheck-test");
+ tester.setInitParameter("cpu-profile-uri", "/pprof-test");
+ tester.addServlet(AdminServlet.class, "/admin");
+ }
+
+ @Before
+ public void setUp() {
+ request.setMethod("GET");
+ request.setURI("/context/admin");
+ request.setVersion("HTTP/1.0");
+ }
+
+ @Test
+ public void returnsA200() throws Exception {
+ processRequest();
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+ assertThat(response.getContent())
+ .isEqualTo(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</title>%n" +
+ "</head>%n" +
+ "<body>%n" +
+ " <h1>Operational Menu</h1>%n" +
+ " <ul>%n" +
+ " <li><a href=\"/context/admin/metrics-test?pretty=true\">Metrics</a></li>%n" +
+ " <li><a href=\"/context/admin/ping-test\">Ping</a></li>%n" +
+ " <li><a href=\"/context/admin/threads-test\">Threads</a></li>%n" +
+ " <li><a href=\"/context/admin/healthcheck-test?pretty=true\">Healthcheck</a></li>%n" +
+ " <li><a href=\"/context/admin/pprof-test\">CPU Profile</a></li>%n" +
+ " <li><a href=\"/context/admin/pprof-test?state=blocked\">CPU Contention</a></li>%n" +
+ " </ul>%n" +
+ "</body>%n" +
+ "</html>%n"
+ ));
+ assertThat(response.get(HttpHeader.CONTENT_TYPE))
+ .isEqualTo("text/html;charset=UTF-8");
+ }
+}
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
index 280672c..6e7de41 100644
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/CpuProfileServletTest.java
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/CpuProfileServletTest.java
@@ -1,6 +1,7 @@
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;
@@ -23,19 +24,19 @@ public class CpuProfileServletTest extends AbstractServletTest {
}
@Test
- public void returns200OK() throws Exception {
+ public void returns200OK() {
assertThat(response.getStatus())
.isEqualTo(200);
}
@Test
- public void returnsPprofRaw() throws Exception {
+ public void returnsPprofRaw() {
assertThat(response.get(HttpHeader.CONTENT_TYPE))
.isEqualTo("pprof/raw");
}
@Test
- public void returnsUncacheable() throws Exception {
+ public void returnsUncacheable() {
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/HealthCheckServletTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/HealthCheckServletTest.java
index 6822879..b3ccc9b 100644
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/HealthCheckServletTest.java
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/HealthCheckServletTest.java
@@ -1,44 +1,77 @@
package com.codahale.metrics.servlets;
+import com.codahale.metrics.Clock;
import com.codahale.metrics.health.HealthCheck;
+import com.codahale.metrics.health.HealthCheckFilter;
import com.codahale.metrics.health.HealthCheckRegistry;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.servlet.ServletTester;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import static org.mockito.Mockito.*;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class HealthCheckServletTest extends AbstractServletTest {
+
+ private static final ZonedDateTime FIXED_TIME = ZonedDateTime.now();
+
+ private static final DateTimeFormatter DATE_TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+
+ private static final String EXPECTED_TIMESTAMP = DATE_TIME_FORMATTER.format(FIXED_TIME);
+
+ private static final Clock FIXED_CLOCK = new Clock() {
+ @Override
+ public long getTick() {
+ return 0L;
+ }
+
+ @Override
+ public long getTime() {
+ return FIXED_TIME.toInstant().toEpochMilli();
+ }
+ };
+
private final HealthCheckRegistry registry = new HealthCheckRegistry();
private final ExecutorService threadPool = Executors.newCachedThreadPool();
+ private final ObjectMapper mapper = new ObjectMapper();
@Override
protected void setUp(ServletTester tester) {
tester.addServlet(HealthCheckServlet.class, "/healthchecks");
tester.setAttribute("com.codahale.metrics.servlets.HealthCheckServlet.registry", registry);
tester.setAttribute("com.codahale.metrics.servlets.HealthCheckServlet.executor", threadPool);
+ tester.setAttribute("com.codahale.metrics.servlets.HealthCheckServlet.mapper", mapper);
+ tester.setAttribute("com.codahale.metrics.servlets.HealthCheckServlet.healthCheckFilter",
+ (HealthCheckFilter) (name, healthCheck) -> !"filtered".equals(name));
}
@Before
- public void setUp() throws Exception {
+ public void setUp() {
request.setMethod("GET");
request.setURI("/healthchecks");
request.setVersion("HTTP/1.0");
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
threadPool.shutdown();
}
@@ -46,83 +79,103 @@ public class HealthCheckServletTest extends AbstractServletTest {
public void returns501IfNoHealthChecksAreRegistered() throws Exception {
processRequest();
- assertThat(response.getStatus())
- .isEqualTo(501);
- assertThat(response.getContent())
- .isEqualTo("{}");
- assertThat(response.get(HttpHeader.CONTENT_TYPE))
- .isEqualTo("application/json");
+ assertThat(response.getStatus()).isEqualTo(501);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent()).isEqualTo("{}");
}
@Test
public void returnsA200IfAllHealthChecksAreHealthy() throws Exception {
- registry.register("fun", new HealthCheck() {
- @Override
- protected Result check() throws Exception {
- return Result.healthy("whee");
- }
- });
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("whee")));
processRequest();
- assertThat(response.getStatus())
- .isEqualTo(200);
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
assertThat(response.getContent())
- .isEqualTo("{\"fun\":{\"healthy\":true,\"message\":\"whee\"}}");
- assertThat(response.get(HttpHeader.CONTENT_TYPE))
- .isEqualTo("application/json");
+ .isEqualTo("{\"fun\":{\"healthy\":true,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" +
+ EXPECTED_TIMESTAMP +
+ "\"}}");
}
@Test
- public void returnsA500IfAnyHealthChecksAreUnhealthy() throws Exception {
- registry.register("fun", new HealthCheck() {
- @Override
- protected Result check() throws Exception {
- return Result.healthy("whee");
- }
- });
-
- registry.register("notFun", new HealthCheck() {
- @Override
- protected Result check() throws Exception {
- return Result.unhealthy("whee");
- }
- });
+ public void returnsASubsetOfHealthChecksIfFiltered() throws Exception {
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("whee")));
+ registry.register("filtered", new TestHealthCheck(() -> unhealthyResultWithMessage("whee")));
processRequest();
- assertThat(response.getStatus())
- .isEqualTo(500);
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
assertThat(response.getContent())
- .isEqualTo("{\"fun\":{\"healthy\":true,\"message\":\"whee\"},\"notFun\":{\"healthy\":false,\"message\":\"whee\"}}");
- assertThat(response.get(HttpHeader.CONTENT_TYPE))
- .isEqualTo("application/json");
+ .isEqualTo("{\"fun\":{\"healthy\":true,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" +
+ EXPECTED_TIMESTAMP +
+ "\"}}");
+ }
+
+ @Test
+ public void returnsA500IfAnyHealthChecksAreUnhealthy() throws Exception {
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("whee")));
+ registry.register("notFun", new TestHealthCheck(() -> unhealthyResultWithMessage("whee")));
+
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(500);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent()).contains(
+ "{\"fun\":{\"healthy\":true,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" + EXPECTED_TIMESTAMP + "\"}",
+ ",\"notFun\":{\"healthy\":false,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" + EXPECTED_TIMESTAMP + "\"}}");
+ }
+
+ @Test
+ public void returnsA200IfAnyHealthChecksAreUnhealthyAndHttpStatusIndicatorIsDisabled() throws Exception {
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("whee")));
+ registry.register("notFun", new TestHealthCheck(() -> unhealthyResultWithMessage("whee")));
+ request.setURI("/healthchecks?httpStatusIndicator=false");
+
+ processRequest();
+
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
+ assertThat(response.getContent()).contains(
+ "{\"fun\":{\"healthy\":true,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" + EXPECTED_TIMESTAMP + "\"}",
+ ",\"notFun\":{\"healthy\":false,\"message\":\"whee\",\"duration\":0,\"timestamp\":\"" + EXPECTED_TIMESTAMP + "\"}}");
}
@Test
public void optionallyPrettyPrintsTheJson() throws Exception {
- registry.register("fun", new HealthCheck() {
- @Override
- protected Result check() throws Exception {
- return Result.healthy("whee");
- }
- });
+ registry.register("fun", new TestHealthCheck(() -> healthyResultWithMessage("foo bar 123")));
request.setURI("/healthchecks?pretty=true");
processRequest();
- assertThat(response.getStatus())
- .isEqualTo(200);
+ assertThat(response.getStatus()).isEqualTo(200);
+ assertThat(response.get(HttpHeader.CONTENT_TYPE)).isEqualTo("application/json");
assertThat(response.getContent())
.isEqualTo(String.format("{%n" +
- " \"fun\" : {%n" +
- " \"healthy\" : true,%n" +
- " \"message\" : \"whee\"%n" +
- " }%n" +
- "}"));
- assertThat(response.get(HttpHeader.CONTENT_TYPE))
- .isEqualTo("application/json");
+ " \"fun\" : {%n" +
+ " \"healthy\" : true,%n" +
+ " \"message\" : \"foo bar 123\",%n" +
+ " \"duration\" : 0,%n" +
+ " \"timestamp\" : \"" + EXPECTED_TIMESTAMP + "\"" +
+ "%n }%n}"));
+ }
+
+ private static HealthCheck.Result healthyResultWithMessage(String message) {
+ return HealthCheck.Result.builder()
+ .healthy()
+ .withMessage(message)
+ .usingClock(FIXED_CLOCK)
+ .build();
+ }
+
+ private static HealthCheck.Result unhealthyResultWithMessage(String message) {
+ return HealthCheck.Result.builder()
+ .unhealthy()
+ .withMessage(message)
+ .usingClock(FIXED_CLOCK)
+ .build();
}
@Test
@@ -131,12 +184,12 @@ public class HealthCheckServletTest extends AbstractServletTest {
final ServletContext servletContext = mock(ServletContext.class);
final ServletConfig servletConfig = mock(ServletConfig.class);
when(servletConfig.getServletContext()).thenReturn(servletContext);
-
+
final HealthCheckServlet healthCheckServlet = new HealthCheckServlet(healthCheckRegistry);
healthCheckServlet.init(servletConfig);
-
+
verify(servletConfig, times(1)).getServletContext();
- verify(servletContext, never()).getAttribute(eq(HealthCheckServlet.HEALTH_CHECK_REGISTRY));
+ verify(servletContext, never()).getAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY);
}
@Test
@@ -145,14 +198,14 @@ public class HealthCheckServletTest extends AbstractServletTest {
final ServletContext servletContext = mock(ServletContext.class);
final ServletConfig servletConfig = mock(ServletConfig.class);
when(servletConfig.getServletContext()).thenReturn(servletContext);
- when(servletContext.getAttribute(eq(HealthCheckServlet.HEALTH_CHECK_REGISTRY)))
- .thenReturn(healthCheckRegistry);
-
+ when(servletContext.getAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY))
+ .thenReturn(healthCheckRegistry);
+
final HealthCheckServlet healthCheckServlet = new HealthCheckServlet(null);
healthCheckServlet.init(servletConfig);
-
- verify(servletConfig, times(2)).getServletContext();
- verify(servletContext, times(1)).getAttribute(eq(HealthCheckServlet.HEALTH_CHECK_REGISTRY));
+
+ verify(servletConfig, times(1)).getServletContext();
+ verify(servletContext, times(1)).getAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY);
}
@Test(expected = ServletException.class)
@@ -160,10 +213,44 @@ public class HealthCheckServletTest extends AbstractServletTest {
final ServletContext servletContext = mock(ServletContext.class);
final ServletConfig servletConfig = mock(ServletConfig.class);
when(servletConfig.getServletContext()).thenReturn(servletContext);
- when(servletContext.getAttribute(eq(HealthCheckServlet.HEALTH_CHECK_REGISTRY)))
- .thenReturn("IRELLEVANT_STRING");
-
+ when(servletContext.getAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY))
+ .thenReturn("IRELLEVANT_STRING");
+
+ final HealthCheckServlet healthCheckServlet = new HealthCheckServlet(null);
+ healthCheckServlet.init(servletConfig);
+ }
+
+ @Test
+ public void constructorWithObjectMapperAsArgumentUsesServletConfigWhenNullButWrongTypeInContext() throws Exception {
+ final ServletContext servletContext = mock(ServletContext.class);
+ final ServletConfig servletConfig = mock(ServletConfig.class);
+ when(servletConfig.getServletContext()).thenReturn(servletContext);
+ when(servletContext.getAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY)).thenReturn(registry);
+ when(servletContext.getAttribute(HealthCheckServlet.HEALTH_CHECK_MAPPER)).thenReturn("IRELLEVANT_STRING");
+
final HealthCheckServlet healthCheckServlet = new HealthCheckServlet(null);
healthCheckServlet.init(servletConfig);
+
+ assertThat(healthCheckServlet.getMapper())
+ .isNotNull()
+ .isInstanceOf(ObjectMapper.class);
+ }
+
+ static class TestHealthCheck extends HealthCheck {
+ private final Callable<Result> check;
+
+ public TestHealthCheck(Callable<Result> check) {
+ this.check = check;
+ }
+
+ @Override
+ protected Result check() throws Exception {
+ return check.call();
+ }
+
+ @Override
+ protected Clock clock() {
+ return FIXED_CLOCK;
+ }
}
}
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 df27344..0bad5f4 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
@@ -1,10 +1,14 @@
package com.codahale.metrics.servlets;
-import com.codahale.metrics.*;
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.servlet.ServletTester;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
@@ -22,7 +26,7 @@ public class MetricsServletContextListenerTest extends AbstractServletTest {
protected void setUp(ServletTester tester) {
tester.setAttribute("com.codahale.metrics.servlets.MetricsServlet.registry", registry);
tester.addServlet(MetricsServlet.class, "/metrics");
- tester.getContext().addEventListener(new MetricsServlet.ContextListener(){
+ tester.getContext().addEventListener(new MetricsServlet.ContextListener() {
@Override
protected MetricRegistry getMetricRegistry() {
return registry;
@@ -46,15 +50,12 @@ public class MetricsServletContextListenerTest extends AbstractServletTest {
}
@Before
- public void setUp() throws Exception {
- when(clock.getTick()).thenReturn(100L, 200L, 300L, 400L);
+ public void setUp() {
+ // provide ticks for the setup (calls getTick 6 times). The serialization in the tests themselves
+ // will call getTick again several times and always get the same value (the last specified here)
+ when(clock.getTick()).thenReturn(100L, 100L, 200L, 300L, 300L, 400L);
- registry.register("g1", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return 100L;
- }
- });
+ registry.register("g1", (Gauge<Long>) () -> 100L);
registry.counter("c").inc();
registry.histogram("h").update(1);
registry.register("m", new Meter(clock)).mark();
@@ -76,7 +77,7 @@ public class MetricsServletContextListenerTest extends AbstractServletTest {
.isEqualTo(allowedOrigin);
assertThat(response.getContent())
.isEqualTo("{" +
- "\"version\":\"3.1.3\"," +
+ "\"version\":\"4.0.0\"," +
"\"gauges\":{" +
"\"g1\":{\"value\":100}" +
"}," +
@@ -106,7 +107,7 @@ public class MetricsServletContextListenerTest extends AbstractServletTest {
.isEqualTo(allowedOrigin);
assertThat(response.getContent())
.isEqualTo(String.format("{%n" +
- " \"version\" : \"3.1.3\",%n" +
+ " \"version\" : \"4.0.0\",%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 c2036df..73bb160 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
@@ -1,20 +1,23 @@
package com.codahale.metrics.servlets;
-import com.codahale.metrics.*;
-
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.servlet.ServletTester;
import org.junit.Before;
import org.junit.Test;
-import java.util.concurrent.TimeUnit;
-
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -35,15 +38,12 @@ public class MetricsServletTest extends AbstractServletTest {
}
@Before
- public void setUp() throws Exception {
- when(clock.getTick()).thenReturn(100L, 200L, 300L, 400L);
+ public void setUp() {
+ // provide ticks for the setup (calls getTick 6 times). The serialization in the tests themselves
+ // will call getTick again several times and always get the same value (the last specified here)
+ when(clock.getTick()).thenReturn(100L, 100L, 200L, 300L, 300L, 400L);
- registry.register("g1", new Gauge<Long>() {
- @Override
- public Long getValue() {
- return 100L;
- }
- });
+ registry.register("g1", (Gauge<Long>) () -> 100L);
registry.counter("c").inc();
registry.histogram("h").update(1);
registry.register("m", new Meter(clock)).mark();
@@ -65,28 +65,28 @@ public class MetricsServletTest extends AbstractServletTest {
.isEqualTo("*");
assertThat(response.getContent())
.isEqualTo("{" +
- "\"version\":\"3.1.3\"," +
- "\"gauges\":{" +
- "\"g1\":{\"value\":100}" +
- "}," +
- "\"counters\":{" +
- "\"c\":{\"count\":1}" +
- "}," +
- "\"histograms\":{" +
- "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
- "}," +
- "\"meters\":{" +
- "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
- "}" +
- "}");
+ "\"version\":\"4.0.0\"," +
+ "\"gauges\":{" +
+ "\"g1\":{\"value\":100}" +
+ "}," +
+ "\"counters\":{" +
+ "\"c\":{\"count\":1}" +
+ "}," +
+ "\"histograms\":{" +
+ "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
+ "}," +
+ "\"meters\":{" +
+ "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
+ "}" +
+ "}");
assertThat(response.get(HttpHeader.CONTENT_TYPE))
.isEqualTo("application/json");
}
@Test
public void returnsJsonWhenJsonpInitParamNotSet() throws Exception {
- String callbackParamName = "callbackParam";
- String callbackParamVal = "callbackParamVal";
+ String callbackParamName = "callbackParam";
+ String callbackParamVal = "callbackParamVal";
request.setURI("/metrics?" + callbackParamName + "=" + callbackParamVal);
processRequest();
@@ -96,28 +96,28 @@ public class MetricsServletTest extends AbstractServletTest {
.isEqualTo("*");
assertThat(response.getContent())
.isEqualTo("{" +
- "\"version\":\"3.1.3\"," +
- "\"gauges\":{" +
- "\"g1\":{\"value\":100}" +
- "}," +
- "\"counters\":{" +
- "\"c\":{\"count\":1}" +
- "}," +
- "\"histograms\":{" +
- "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
- "}," +
- "\"meters\":{" +
- "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
- "}" +
- "}");
+ "\"version\":\"4.0.0\"," +
+ "\"gauges\":{" +
+ "\"g1\":{\"value\":100}" +
+ "}," +
+ "\"counters\":{" +
+ "\"c\":{\"count\":1}" +
+ "}," +
+ "\"histograms\":{" +
+ "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
+ "}," +
+ "\"meters\":{" +
+ "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
+ "}" +
+ "}");
assertThat(response.get(HttpHeader.CONTENT_TYPE))
.isEqualTo("application/json");
}
@Test
public void returnsJsonpWhenInitParamSet() throws Exception {
- String callbackParamName = "callbackParam";
- String callbackParamVal = "callbackParamVal";
+ String callbackParamName = "callbackParam";
+ String callbackParamVal = "callbackParamVal";
request.setURI("/metrics?" + callbackParamName + "=" + callbackParamVal);
tester.getContext().setInitParameter("com.codahale.metrics.servlets.MetricsServlet.jsonpCallback", callbackParamName);
processRequest();
@@ -127,20 +127,20 @@ public class MetricsServletTest extends AbstractServletTest {
.isEqualTo("*");
assertThat(response.getContent())
.isEqualTo(callbackParamVal + "({" +
- "\"version\":\"3.1.3\"," +
- "\"gauges\":{" +
- "\"g1\":{\"value\":100}" +
- "}," +
- "\"counters\":{" +
- "\"c\":{\"count\":1}" +
- "}," +
- "\"histograms\":{" +
- "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
- "}," +
- "\"meters\":{" +
- "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
- "}" +
- "})");
+ "\"version\":\"4.0.0\"," +
+ "\"gauges\":{" +
+ "\"g1\":{\"value\":100}" +
+ "}," +
+ "\"counters\":{" +
+ "\"c\":{\"count\":1}" +
+ "}," +
+ "\"histograms\":{" +
+ "\"h\":{\"count\":1,\"max\":1,\"mean\":1.0,\"min\":1,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0}" +
+ "}," +
+ "\"meters\":{" +
+ "\"m\":{\"count\":1,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":3333333.3333333335,\"units\":\"events/second\"}},\"timers\":{\"t\":{\"count\":1,\"max\":1.0,\"mean\":1.0,\"min\":1.0,\"p50\":1.0,\"p75\":1.0,\"p95\":1.0,\"p98\":1.0,\"p99\":1.0,\"p999\":1.0,\"stddev\":0.0,\"m15_rate\":0.0,\"m1_rate\":0.0,\"m5_rate\":0.0,\"mean_rate\":1.0E7,\"duration_units\":\"seconds\",\"rate_units\":\"calls/second\"}" +
+ "}" +
+ "})");
assertThat(response.get(HttpHeader.CONTENT_TYPE))
.isEqualTo("application/json");
}
@@ -157,64 +157,64 @@ public class MetricsServletTest extends AbstractServletTest {
.isEqualTo("*");
assertThat(response.getContent())
.isEqualTo(String.format("{%n" +
- " \"version\" : \"3.1.3\",%n" +
- " \"gauges\" : {%n" +
- " \"g1\" : {%n" +
- " \"value\" : 100%n" +
- " }%n" +
- " },%n" +
- " \"counters\" : {%n" +
- " \"c\" : {%n" +
- " \"count\" : 1%n" +
- " }%n" +
- " },%n" +
- " \"histograms\" : {%n" +
- " \"h\" : {%n" +
- " \"count\" : 1,%n" +
- " \"max\" : 1,%n" +
- " \"mean\" : 1.0,%n" +
- " \"min\" : 1,%n" +
- " \"p50\" : 1.0,%n" +
- " \"p75\" : 1.0,%n" +
- " \"p95\" : 1.0,%n" +
- " \"p98\" : 1.0,%n" +
- " \"p99\" : 1.0,%n" +
- " \"p999\" : 1.0,%n" +
- " \"stddev\" : 0.0%n" +
- " }%n" +
- " },%n" +
- " \"meters\" : {%n" +
- " \"m\" : {%n" +
- " \"count\" : 1,%n" +
- " \"m15_rate\" : 0.0,%n" +
- " \"m1_rate\" : 0.0,%n" +
- " \"m5_rate\" : 0.0,%n" +
- " \"mean_rate\" : 3333333.3333333335,%n" +
- " \"units\" : \"events/second\"%n" +
- " }%n" +
- " },%n" +
- " \"timers\" : {%n" +
- " \"t\" : {%n" +
- " \"count\" : 1,%n" +
- " \"max\" : 1.0,%n" +
- " \"mean\" : 1.0,%n" +
- " \"min\" : 1.0,%n" +
- " \"p50\" : 1.0,%n" +
- " \"p75\" : 1.0,%n" +
- " \"p95\" : 1.0,%n" +
- " \"p98\" : 1.0,%n" +
- " \"p99\" : 1.0,%n" +
- " \"p999\" : 1.0,%n" +
- " \"stddev\" : 0.0,%n" +
- " \"m15_rate\" : 0.0,%n" +
- " \"m1_rate\" : 0.0,%n" +
- " \"m5_rate\" : 0.0,%n" +
- " \"mean_rate\" : 1.0E7,%n" +
- " \"duration_units\" : \"seconds\",%n" +
- " \"rate_units\" : \"calls/second\"%n" +
- " }%n" +
- " }%n" +
- "}"));
+ " \"version\" : \"4.0.0\",%n" +
+ " \"gauges\" : {%n" +
+ " \"g1\" : {%n" +
+ " \"value\" : 100%n" +
+ " }%n" +
+ " },%n" +
+ " \"counters\" : {%n" +
+ " \"c\" : {%n" +
+ " \"count\" : 1%n" +
+ " }%n" +
+ " },%n" +
+ " \"histograms\" : {%n" +
+ " \"h\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"max\" : 1,%n" +
+ " \"mean\" : 1.0,%n" +
+ " \"min\" : 1,%n" +
+ " \"p50\" : 1.0,%n" +
+ " \"p75\" : 1.0,%n" +
+ " \"p95\" : 1.0,%n" +
+ " \"p98\" : 1.0,%n" +
+ " \"p99\" : 1.0,%n" +
+ " \"p999\" : 1.0,%n" +
+ " \"stddev\" : 0.0%n" +
+ " }%n" +
+ " },%n" +
+ " \"meters\" : {%n" +
+ " \"m\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"m15_rate\" : 0.0,%n" +
+ " \"m1_rate\" : 0.0,%n" +
+ " \"m5_rate\" : 0.0,%n" +
+ " \"mean_rate\" : 3333333.3333333335,%n" +
+ " \"units\" : \"events/second\"%n" +
+ " }%n" +
+ " },%n" +
+ " \"timers\" : {%n" +
+ " \"t\" : {%n" +
+ " \"count\" : 1,%n" +
+ " \"max\" : 1.0,%n" +
+ " \"mean\" : 1.0,%n" +
+ " \"min\" : 1.0,%n" +
+ " \"p50\" : 1.0,%n" +
+ " \"p75\" : 1.0,%n" +
+ " \"p95\" : 1.0,%n" +
+ " \"p98\" : 1.0,%n" +
+ " \"p99\" : 1.0,%n" +
+ " \"p999\" : 1.0,%n" +
+ " \"stddev\" : 0.0,%n" +
+ " \"m15_rate\" : 0.0,%n" +
+ " \"m1_rate\" : 0.0,%n" +
+ " \"m5_rate\" : 0.0,%n" +
+ " \"mean_rate\" : 1.0E7,%n" +
+ " \"duration_units\" : \"seconds\",%n" +
+ " \"rate_units\" : \"calls/second\"%n" +
+ " }%n" +
+ " }%n" +
+ "}"));
assertThat(response.get(HttpHeader.CONTENT_TYPE))
.isEqualTo("application/json");
}
@@ -240,7 +240,7 @@ public class MetricsServletTest extends AbstractServletTest {
final ServletConfig servletConfig = mock(ServletConfig.class);
when(servletConfig.getServletContext()).thenReturn(servletContext);
when(servletContext.getAttribute(eq(MetricsServlet.METRICS_REGISTRY)))
- .thenReturn(metricRegistry);
+ .thenReturn(metricRegistry);
final MetricsServlet metricsServlet = new MetricsServlet(null);
metricsServlet.init(servletConfig);
@@ -255,7 +255,7 @@ public class MetricsServletTest extends AbstractServletTest {
final ServletConfig servletConfig = mock(ServletConfig.class);
when(servletConfig.getServletContext()).thenReturn(servletContext);
when(servletContext.getAttribute(eq(MetricsServlet.METRICS_REGISTRY)))
- .thenReturn("IRELLEVANT_STRING");
+ .thenReturn("IRELLEVANT_STRING");
final MetricsServlet metricsServlet = new MetricsServlet(null);
metricsServlet.init(servletConfig);
diff --git a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/PingServletTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/PingServletTest.java
index e0b2567..658e00a 100644
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/PingServletTest.java
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/PingServletTest.java
@@ -14,7 +14,7 @@ public class PingServletTest extends AbstractServletTest {
}
@Before
- public void setUp() throws Exception {
+ public void setUp() throws Exception {
request.setMethod("GET");
request.setURI("/ping");
request.setVersion("HTTP/1.0");
@@ -23,25 +23,25 @@ public class PingServletTest extends AbstractServletTest {
}
@Test
- public void returns200OK() throws Exception {
+ public void returns200OK() {
assertThat(response.getStatus())
.isEqualTo(200);
}
@Test
- public void returnsPong() throws Exception {
+ public void returnsPong() {
assertThat(response.getContent())
.isEqualTo(String.format("pong%n"));
}
@Test
- public void returnsTextPlain() throws Exception {
+ public void returnsTextPlain() {
assertThat(response.get(HttpHeader.CONTENT_TYPE))
- .isEqualTo("text/plain; charset=ISO-8859-1");
+ .isEqualTo("text/plain;charset=ISO-8859-1");
}
@Test
- public void returnsUncacheable() throws Exception {
+ public void returnsUncacheable() {
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/ThreadDumpServletTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/ThreadDumpServletTest.java
index b8b96dd..0ad5756 100644
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/ThreadDumpServletTest.java
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/ThreadDumpServletTest.java
@@ -23,25 +23,25 @@ public class ThreadDumpServletTest extends AbstractServletTest {
}
@Test
- public void returns200OK() throws Exception {
+ public void returns200OK() {
assertThat(response.getStatus())
.isEqualTo(200);
}
@Test
- public void returnsAThreadDump() throws Exception {
+ public void returnsAThreadDump() {
assertThat(response.getContent())
.contains("Finalizer");
}
@Test
- public void returnsTextPlain() throws Exception {
+ public void returnsTextPlain() {
assertThat(response.get(HttpHeader.CONTENT_TYPE))
.isEqualTo("text/plain");
}
@Test
- public void returnsUncacheable() throws Exception {
+ public void returnsUncacheable() {
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/experiments/ExampleServer.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/experiments/ExampleServer.java
index cb3c0cc..1193063 100644
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/experiments/ExampleServer.java
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/experiments/ExampleServer.java
@@ -22,16 +22,12 @@ import static com.codahale.metrics.MetricRegistry.name;
public class ExampleServer {
private static final MetricRegistry REGISTRY = new MetricRegistry();
- private static final Counter COUNTER_1 = REGISTRY.counter(name(ExampleServer.class,
- "wah",
- "doody"));
+ private static final Counter COUNTER_1 = REGISTRY.counter(name(ExampleServer.class, "wah", "doody"));
private static final Counter COUNTER_2 = REGISTRY.counter(name(ExampleServer.class, "woo"));
+
static {
- REGISTRY.register(name(ExampleServer.class, "boo"), new Gauge<Integer>() {
- @Override
- public Integer getValue() {
- throw new RuntimeException("asplode!");
- }
+ REGISTRY.register(name(ExampleServer.class, "boo"), (Gauge<Integer>) () -> {
+ throw new RuntimeException("asplode!");
});
}
@@ -42,9 +38,8 @@ public class ExampleServer {
final ThreadPool threadPool = new InstrumentedQueuedThreadPool(REGISTRY);
final Server server = new Server(threadPool);
- final Connector connector = new ServerConnector(server,
- new InstrumentedConnectionFactory(new HttpConnectionFactory(),
- REGISTRY.timer("http.connection")));
+ final Connector connector = new ServerConnector(server, new InstrumentedConnectionFactory(
+ new HttpConnectionFactory(), REGISTRY.timer("http.connection")));
server.addConnector(connector);
final ServletContextHandler context = new ServletContextHandler();
@@ -58,7 +53,7 @@ public class ExampleServer {
final InstrumentedHandler handler = new InstrumentedHandler(REGISTRY);
handler.setHandler(context);
server.setHandler(handler);
-
+
server.start();
server.join();
}
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000..8d937f4
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000..f80fbad
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
index 3c3b3c1..87169f2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,58 +1,80 @@
<?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>
- <prerequisites>
- <maven>3.0.1</maven>
- </prerequisites>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-parent</artifactId>
- <version>3.2.6</version>
+ <version>4.2.25</version>
<packaging>pom</packaging>
<name>Metrics Parent</name>
<description>
The Metrics library.
</description>
- <url>http://metrics.dropwizard.io/</url>
+ <url>https://metrics.dropwizard.io</url>
<modules>
<module>docs</module>
+ <module>metrics-bom</module>
<module>metrics-annotation</module>
<module>metrics-benchmarks</module>
+ <module>metrics-caffeine</module>
+ <module>metrics-caffeine3</module>
<module>metrics-core</module>
- <module>metrics-healthchecks</module>
+ <module>metrics-collectd</module>
<module>metrics-ehcache</module>
- <module>metrics-ganglia</module>
<module>metrics-graphite</module>
+ <module>metrics-healthchecks</module>
<module>metrics-httpclient</module>
+ <module>metrics-httpclient5</module>
<module>metrics-httpasyncclient</module>
+ <module>metrics-jakarta-servlet</module>
+ <module>metrics-jakarta-servlet6</module>
+ <module>metrics-jakarta-servlets</module>
<module>metrics-jcache</module>
+ <module>metrics-jcstress</module>
<module>metrics-jdbi</module>
- <module>metrics-jersey</module>
+ <module>metrics-jdbi3</module>
<module>metrics-jersey2</module>
- <module>metrics-jetty8</module>
+ <module>metrics-jersey3</module>
+ <module>metrics-jersey31</module>
<module>metrics-jetty9</module>
- <module>metrics-jetty9-legacy</module>
+ <module>metrics-jetty10</module>
+ <module>metrics-jetty11</module>
+ <module>metrics-jmx</module>
<module>metrics-json</module>
<module>metrics-jvm</module>
- <module>metrics-log4j</module>
<module>metrics-log4j2</module>
<module>metrics-logback</module>
+ <module>metrics-logback13</module>
+ <module>metrics-logback14</module>
<module>metrics-servlet</module>
<module>metrics-servlets</module>
- <module>metrics-jcstress</module>
- </modules>
+ </modules>
<properties>
+ <project.build.outputTimestamp>2024-01-24T22:47:57Z</project.build.outputTimestamp>
<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.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.6.6</rabbitmq.version>
+
+ <jetty9.version>9.4.53.v20231009</jetty9.version>
+ <jetty10.version>10.0.19</jetty10.version>
+ <jetty11.version>11.0.19</jetty11.version>
+ <jetty12.version>12.0.5</jetty12.version>
+ <slf4j.version>1.7.36</slf4j.version>
+ <assertj.version>3.25.2</assertj.version>
+ <byte-buddy.version>1.14.11</byte-buddy.version>
+ <mockito.version>5.9.0</mockito.version>
+ <junit.version>4.13.1</junit.version>
+ <commons-lang3.version>3.14.0</commons-lang3.version>
+ <maven-compiler-plugin.version>3.12.1</maven-compiler-plugin.version>
+ <errorprone.version>2.24.1</errorprone.version>
+ <errorprone.javac.version>9+181-r4173-1</errorprone.javac.version>
+ <servlet6.version>6.0.0</servlet6.version>
+
+ <sonar.projectKey>dropwizard_metrics</sonar.projectKey>
+ <sonar.organization>dropwizard</sonar.organization>
+ <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+ <sonar.moduleKey>${project.artifactId}</sonar.moduleKey>
</properties>
<developers>
@@ -72,12 +94,20 @@
<role>committer</role>
</roles>
</developer>
+ <developer>
+ <name>Artem Prigoda</name>
+ <email>prigoda.artem@ya.ru</email>
+ <timezone>Europe/Berlin</timezone>
+ <roles>
+ <role>committer</role>
+ </roles>
+ </developer>
</developers>
<licenses>
<license>
<name>Apache License 2.0</name>
- <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
+ <url>https://www.apache.org/licenses/LICENSE-2.0.html</url>
<distribution>repo</distribution>
</license>
</licenses>
@@ -85,85 +115,85 @@
<scm>
<connection>scm:git:git://github.com/dropwizard/metrics.git</connection>
<developerConnection>scm:git:git@github.com:dropwizard/metrics.git</developerConnection>
- <url>http://github.com/dropwizard/metrics/</url>
- <tag>v3.2.6</tag>
+ <url>https://github.com/dropwizard/metrics/</url>
+ <tag>v4.2.25</tag>
</scm>
<issueManagement>
<system>github</system>
- <url>http://github.com/dropwizard/metrics/issues#issue/</url>
+ <url>https://github.com/dropwizard/metrics/issues/</url>
</issueManagement>
<distributionManagement>
<snapshotRepository>
- <id>sonatype-nexus-snapshots</id>
+ <id>ossrh</id>
<name>Sonatype Nexus Snapshots</name>
- <url>http://oss.sonatype.org/content/repositories/snapshots</url>
+ <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
- <id>sonatype-nexus-staging</id>
+ <id>ossrh</id>
<name>Nexus Release Repository</name>
- <url>http://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>${slf4j.version}</version>
- </dependency>
- </dependencies>
- </dependencyManagement>
-
- <dependencies>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.11</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.assertj</groupId>
- <artifactId>assertj-core</artifactId>
- <version>1.6.1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-all</artifactId>
- <version>1.9.5</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
- <version>${slf4j.version}</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
-
<profiles>
<profile>
- <id>doclint-java8-disable</id>
+ <id>jdk8</id>
<activation>
- <jdk>[1.8,)</jdk>
+ <jdk>1.8</jdk>
</activation>
-
<build>
<plugins>
- <!-- java 8 doclint html verification is excluded to suppress strict HTML 4.0 compliance errors
- like "error: self-closing element not allowed <p />" -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs combine.children="append">
+ <arg>-J-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${errorprone.javac.version}/javac-${errorprone.javac.version}.jar</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>jdk17</id>
+ <activation>
+ <jdk>[17,)</jdk>
+ </activation>
+ <modules>
+ <module>metrics-jetty12</module>
+ <module>metrics-jetty12-ee10</module>
+ </modules>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
<configuration>
- <additionalparam>-Xdoclint:all -Xdoclint:-html</additionalparam>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-XDcompilePolicy=simple</arg>
+ <arg>-Xplugin:ErrorProne -XepExcludedPaths:.*/target/generated-sources/.*</arg>
+ <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
+ <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
+ <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
+ <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
+ <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
+ <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
+ <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
+ <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
+ <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
+ <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
+ </compilerArgs>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>com.google.errorprone</groupId>
+ <artifactId>error_prone_core</artifactId>
+ <version>${errorprone.version}</version>
+ </path>
+ </annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
@@ -177,15 +207,44 @@
<value>true</value>
</property>
</activation>
+ <properties>
+ <gpg.keyname>EDA86E9FB607B5FC9223FB767D4868B53E31E7AD</gpg.keyname>
+ </properties>
<build>
<plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.3.0</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
- <version>1.6</version>
+ <version>3.1.0</version>
<configuration>
<gpgArguments>
- <argument>--no-tty</argument>
+ <arg>--pinentry-mode</arg>
+ <arg>loopback</arg>
</gpgArguments>
</configuration>
<executions>
@@ -198,26 +257,97 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ <version>1.6.13</version>
+ <configuration>
+ <serverId>ossrh</serverId>
+ <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
+ <autoReleaseAfterClose>true</autoReleaseAfterClose>
+ </configuration>
+ <executions>
+ <execution>
+ <id>nexus-deploy</id>
+ <phase>deploy</phase>
+ <goals>
+ <goal>deploy</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.cyclonedx</groupId>
+ <artifactId>cyclonedx-maven-plugin</artifactId>
+ <version>2.7.11</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>makeAggregateBom</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven-compiler-plugin.version}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>3.1.1</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>3.6.3</version>
+ <configuration>
+ <source>8</source>
+ <doclint>none</doclint>
+ <quiet>true</quiet>
+ <notimestamp>true</notimestamp>
+ <legacyMode>true</legacyMode>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.1</version>
<configuration>
- <source>1.6</source>
- <target>1.6</target>
+ <release>8</release>
+ <fork>true</fork>
+ <parameters>true</parameters>
+ <showWarnings>true</showWarnings>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-XDcompilePolicy=simple</arg>
+ <arg>-Xplugin:ErrorProne -XepExcludedPaths:.*/target/generated-sources/.*</arg>
+ </compilerArgs>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>com.google.errorprone</groupId>
+ <artifactId>error_prone_core</artifactId>
+ <version>${errorprone.version}</version>
+ </path>
+ </annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>2.3.7</version>
+ <version>5.1.9</version>
<extensions>true</extensions>
<configuration>
<instructions>
@@ -225,37 +355,26 @@
javax.servlet*;version="[2.5.0,4.0.0)",
org.slf4j*;version="[1.6.0,2.0.0)",
sun.misc.*;resolution:=optional,
+ com.sun.management.*;resolution:=optional,
*
]]>
</Import-Package>
+ <Automatic-Module-Name>${javaModuleName}</Automatic-Module-Name>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.14.1</version>
+ <version>3.2.5</version>
<configuration>
- <parallel>classes</parallel>
+ <argLine>@{argLine} -Djava.net.preferIPv4Stack=true</argLine>
</configuration>
</plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- <version>2.2.1</version>
- <executions>
- <execution>
- <id>attach-sources</id>
- <goals>
- <goal>jar</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
- <version>1.2</version>
+ <version>3.4.1</version>
<executions>
<execution>
<id>enforce</id>
@@ -272,21 +391,28 @@
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
- <version>2.9</version>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>3.6.1</version>
<executions>
<execution>
- <id>attach-javadocs</id>
+ <id>analyze</id>
<goals>
- <goal>jar</goal>
+ <goal>analyze-only</goal>
+ <goal>analyze-dep-mgt</goal>
+ <goal>analyze-duplicate</goal>
</goals>
+ <phase>verify</phase>
+ <configuration>
+ <failOnWarning>true</failOnWarning>
+ <ignoreNonCompile>true</ignoreNonCompile>
+ </configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
- <version>2.5.3</version>
+ <version>3.0.1</version>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
<mavenExecutorId>forked-path</mavenExecutorId>
@@ -294,35 +420,59 @@
<preparationGoals>clean test</preparationGoals>
</configuration>
</plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <version>3.0.0</version>
- <configuration>
- <effort>Max</effort>
- <threshold>Default</threshold>
- <xmlOutput>true</xmlOutput>
- </configuration>
- <executions>
- <execution>
- <goals>
- <goal>check</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
- <version>2.4</version>
+ <version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
+ <manifestEntries>
+ <Automatic-Module-Name>${javaModuleName}</Automatic-Module-Name>
+ </manifestEntries>
</archive>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>3.3.1</version>
+ <configuration>
+ <configLocation>checkstyle.xml</configLocation>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>3.12.1</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-project-info-reports-plugin</artifactId>
+ <version>3.5.0</version>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.8.11</version>
+ <executions>
+ <execution>
+ <id>prepare-agent</id>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>report</id>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
</project>
diff --git a/prepare_docs.sh b/prepare_docs.sh
new file mode 100755
index 0000000..1130258
--- /dev/null
+++ b/prepare_docs.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+#
+# Builds the documentation of the Metrics project for the specified
+# release, copies and commits it to the local gh-pages branch.
+#
+# Usage: ./prepare_docs.sh v1.0.1 1.0.1
+#
+
+set -e
+
+[[ "$#" < 2 ]] && { echo "No release branch and number are specified"; exit 1; }
+
+release_branch="$1"
+release_number="$2"
+
+echo -e "\nGenerating Dropwizard documentation"
+echo "Release branch: $release_branch"
+echo "Release number: $release_number"
+
+echo -e "\n-------------------------------"
+echo "Moving to $release_branch branch"
+echo "-------------------------------"
+
+git checkout "$release_branch"
+
+echo -e "\n-------------------------------"
+echo "Generating documentation"
+echo -e "-------------------------------\n"
+
+cd docs/
+
+echo -e "\n-------------------------------"
+echo "Staging documentation"
+echo "-------------------------------"
+mvn clean package
+mvn site:site
+
+echo -e "\n-------------------------------"
+echo "Moving to the gh-pages branch"
+echo -e "-------------------------------\n"
+git checkout gh-pages
+
+echo -e "\n-------------------------------"
+echo "Creating a directory for documentation"
+echo -e "-------------------------------\n"
+mkdir "$release_number"
+
+echo -e "\n-------------------------------"
+echo "Copy documentation"
+echo -e "-------------------------------\n"
+cd ../
+cp -r docs/target/site/* "${release_number}"/
+
+echo -e "\n-------------------------------"
+echo "Add and commit changes to the repository"
+echo -e "-------------------------------\n"
+git add .
+git commit -m "Add docs for Dropwizard $release_number"
+
+echo -e "\nDone!"
+echo "Please review changes and push them with if they look good"
+exit $?
+
diff --git a/qodana.yaml b/qodana.yaml
new file mode 100644
index 0000000..112c0b0
--- /dev/null
+++ b/qodana.yaml
@@ -0,0 +1,7 @@
+---
+# https://www.jetbrains.com/help/qodana/qodana-yaml.html
+version: "1.0"
+profile:
+ name: qodana.starter
+projectJDK: 17
+linter: jetbrains/qodana-jvm-community:2023.2
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..acea8eb
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "local>dropwizard/renovate-config"
+ ],
+ "baseBranches": ["release/4.2.x", "release/5.0.x"],
+ "vulnerabilityAlerts": {
+ "labels": ["security"],
+ "assignees": ["team:committers", "team:metrics"]
+ }
+}