├── .gitignore ├── LICENSE.txt ├── README.md ├── RELEASE-NOTES.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ └── com │ └── palominolabs │ └── metrics │ └── guice │ ├── CountedInterceptor.java │ ├── CountedListener.java │ ├── DeclaredMethodsTypeListener.java │ ├── DeclaringClassMetricNamer.java │ ├── ExceptionMeteredInterceptor.java │ ├── ExceptionMeteredListener.java │ ├── GaugeInjectionListener.java │ ├── GaugeInstanceClassMetricNamer.java │ ├── GaugeListener.java │ ├── MeteredInterceptor.java │ ├── MeteredListener.java │ ├── MetricNamer.java │ ├── MetricsInstrumentationModule.java │ ├── TimedInterceptor.java │ ├── TimedListener.java │ └── annotation │ ├── AnnotationResolver.java │ ├── ClassAnnotationResolver.java │ ├── ListAnnotationResolver.java │ └── MethodAnnotationResolver.java └── test └── java └── com └── palominolabs └── metrics └── guice ├── CountInvocationGenericSubtypeTest.java ├── CountedTest.java ├── DeclaringClassNamerGaugeTest.java ├── ExceptionMeteredTest.java ├── GaugeInheritanceTest.java ├── GaugeInstanceClassNamerTest.java ├── GaugeTestBase.java ├── GenericThing.java ├── InstrumentedWithCounter.java ├── InstrumentedWithCounterParent.java ├── InstrumentedWithExceptionMetered.java ├── InstrumentedWithGauge.java ├── InstrumentedWithGaugeParent.java ├── InstrumentedWithMetered.java ├── InstrumentedWithTimed.java ├── MatcherTest.java ├── MeteredTest.java ├── MyException.java ├── StringThing.java ├── TimedTest.java └── annotation ├── ClassAnnotationResolverTest.java ├── ListAnnotationResolverTest.java └── MethodAnnotationResolverTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.ipr 4 | *.iws 5 | .directory 6 | /out 7 | .~lock.*.od*# 8 | build 9 | .gradle 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | # Copyfree Open Innovation License 2 | 3 | This is version 0.6 of the Copyfree Open Innovation License. 4 | 5 | ## Terms and Conditions 6 | 7 | Redistributions, modified or unmodified, in whole or in part, must retain 8 | applicable notices of copyright or other legal privilege, these conditions, and 9 | the following license terms and disclaimer. Subject to these conditions, each 10 | holder of copyright or other legal privileges, author or assembler, and 11 | contributor of this work, henceforth "licensor", hereby grants to any person 12 | who obtains a copy of this work in any form: 13 | 14 | 1. Permission to reproduce, modify, distribute, publish, sell, sublicense, use, 15 | and/or otherwise deal in the licensed material without restriction. 16 | 17 | 2. A perpetual, worldwide, non-exclusive, royalty-free, gratis, irrevocable 18 | patent license to make, have made, provide, transfer, import, use, and/or 19 | otherwise deal in the licensed material without restriction, for any and all 20 | patents held by such licensor and necessarily infringed by the form of the work 21 | upon distribution of that licensor's contribution to the work under the terms 22 | of this license. 23 | 24 | NO WARRANTY OF ANY KIND IS IMPLIED BY, OR SHOULD BE INFERRED FROM, THIS LICENSE 25 | OR THE ACT OF DISTRIBUTION UNDER THE TERMS OF THIS LICENSE, INCLUDING BUT NOT 26 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, 27 | AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS, ASSEMBLERS, OR HOLDERS OF 28 | COPYRIGHT OR OTHER LEGAL PRIVILEGE BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER 29 | LIABILITY, WHETHER IN ACTION OF CONTRACT, TORT, OR OTHERWISE ARISING FROM, OUT 30 | OF, OR IN CONNECTION WITH THE WORK OR THE USE OF OR OTHER DEALINGS IN THE WORK. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | ### Get the artifacts 4 | 5 | Artifacts are released in Maven Central. 6 | 7 | Maven: 8 | 9 | ```xml 10 | 11 | com.palominolabs.metrics 12 | metrics-guice 13 | [the latest version] 14 | 15 | ``` 16 | 17 | Gradle: 18 | 19 | ``` 20 | compile 'com.palominolabs.metrics:metrics-guice:[the latest version]' 21 | ``` 22 | 23 | ### Install the Guice module 24 | 25 | ```java 26 | // somewhere in your Guice module setup 27 | install(MetricsInstrumentationModule.builder().withMetricRegistry(yourFavoriteMetricRegistry).build()); 28 | ``` 29 | 30 | ### Use it 31 | 32 | The `MetricsInstrumentationModule` you installed above will create and appropriately invoke a [Timer](https://dropwizard.github.io/metrics/3.1.0/manual/core/#timers) for `@Timed` methods, a [Meter](https://dropwizard.github.io/metrics/3.1.0/manual/core/#meters) for `@Metered` methods, a [Counter](https://dropwizard.github.io/metrics/3.1.0/manual/core/#counters) for `@Counted` methods, and a [Gauge](https://dropwizard.github.io/metrics/3.1.0/manual/core/#gauges) for `@Gauge` methods. `@ExceptionMetered` is also supported; this creates a `Meter` that measures how often a method throws exceptions. 33 | 34 | The annotations have some configuration options available for metric name, etc. You can also provide a custom `MetricNamer` implementation if the default name scheme does not work for you. 35 | 36 | ### Customizing annotation lookup 37 | 38 | By default `MetricsInstrumentationModule` will provide metrics only for annotated methods. You can also look for annotations on the enclosing classes, or both, or provide your own custom logic. To change annotation resolution, provide an `AnnotationResolver` when building the `MetricsInstrumentationModule`. `MethodAnnotationResolver` is the default implementation. `ClassAnnotationResolver` will look for annotations on the class instead of the method. You can invoke multiple resolvers in order with `ListAnnotationResolver `, so if you wanted to look in methods first and then the class, you could do that: 39 | 40 | ```java 41 | // somewhere in your Guice module setup 42 | install( 43 | MetricsInstrumentationModule.builder() 44 | .withMetricRegistry(yourFavoriteMetricRegistry) 45 | .withAnnotationResolver(new ListAnnotationResolver(Lists.newArrayList(new ClassAnnotationResolver(), new MethodAnnotationResolver())) 46 | .build() 47 | ); 48 | ``` 49 | 50 | ### Metric namer 51 | 52 | The default `MetricNamer` implementation probably does what you want out of the box, but you can also write and use your own. 53 | 54 | #### Example 55 | 56 | If you have a method like this: 57 | 58 | ```java 59 | class SuperCriticalFunctionality { 60 | public void doSomethingImportant() { 61 | // critical business logic 62 | } 63 | } 64 | ``` 65 | 66 | and you want to use a [Timer](https://dropwizard.github.io/metrics/3.1.0/manual/core/#timers) to measure duration, etc, you could always do it by hand: 67 | 68 | ```java 69 | public void doSomethingImportant() { 70 | // timer is some Timer instance 71 | Timer.Context context = timer.time(); 72 | try { 73 | // critical business logic 74 | } finally { 75 | context.stop(); 76 | } 77 | } 78 | ``` 79 | 80 | However, if you're instantiating that class with Guice, you could just do this: 81 | 82 | ```java 83 | @Timed 84 | public void doSomethingImportant() { 85 | // critical business logic 86 | } 87 | ``` 88 | 89 | ### Limitations 90 | 91 | Since this uses Guice AOP, instances must be created by Guice; see [the Guice wiki](https://github.com/google/guice/wiki/AOP). This means that using a Provider where you create the instance won't work, or binding a singleton to an instance, etc. 92 | 93 | Guice AOP doesn't allow us to intercept method calls to annotated methods in supertypes, so `@Counted`, etc, will not have metrics generated for them if they are in supertypes of the injectable class. One small consolation is that `@Gauge` methods can be anywhere in the type hierarchy since they work differently from the other metrics (the generated Gauge object invokes the `java.lang.reflect.Method` directly, so we can call the supertype method unambiguously). 94 | 95 | One common way users might hit this issue is if when trying to use `@Counted`, etc on a JAX-RS resource annotated with `@Path`, `@GET`, etc. This may pose problems for the JAX-RS implementation because the thing it has at runtime is now an auto-generated proxy class, not the "normal" class. A perfectly reasonable approach is instead to handle metrics generation for those classes via the hooks available in the JAX-RS implementation. For Jersey 2, might I suggest [jersey2-metrics](https://bitbucket.org/marshallpierce/jersey2-metrics)? 96 | 97 | # History 98 | 99 | This module started from the state of metrics-guice immediately before it was removed from the [main metrics repo](https://github.com/dropwizard/metrics) in [dropwizard/metrics@e058f76dabf3f805d1c220950a4f42c2ec605ecd](https://github.com/dropwizard/metrics/commit/e058f76dabf3f805d1c220950a4f42c2ec605ecd). 100 | 101 | -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | - 5.0.1 2 | - Publish to maven central 3 | - Build system updates 4 | - 5.0.0 5 | - Build system updates; update Gradle to 6.8 6 | - Update to Guice 4.2.3 7 | - Update to Metrics 5.0.0 8 | - 4.0.0 9 | - Build system updates; update Gradle to 4.6 10 | - Update to Metrics 4.0.2 11 | - Update to Java 8 12 | - 3.3.0 13 | - Change default `MetricNamer` implementation to one that is friendlier to re-using superclass gauges in multiple child classes. The previous behavior is still available as `DeclaringClassMetricNamer`. 14 | - Change `MetricNamer.getNameForGauge` to take an additional parameter to support the superclass logic above. 15 | - Add Animal Sniffer to build to ensure that only JDK6 types are used. 16 | - Update to Gradle 4.0.1 17 | - 3.2.2 18 | - Update to Metrics 3.2.3 19 | - Update to Gradle 4.0 20 | - 3.2.1 21 | - Update to Metrics 3.2 and SLF4J 1.7.25 22 | - Update to Gradle 3.4 23 | - 3.2.0 24 | - Update to Gradle 3.1 25 | - Updated dependencies: SLF4J 1.7.21, Guice 4.1.0 26 | - Allow customization in how annotations are resolved for a method. The default is the previous behavior (only looks on the method itself), but implementations for looking on the class and combining multiple resolvers are provided. 27 | - `MetricsInstrumentationModule` is now constructed builder-style. 28 | - 3.1.4 29 | - Switch to releasing in bintray 30 | - Remove superclass traversal when looking for annotated methods to intercept because AOP on superclass methods doesn't appear to work anyway 31 | - Switch build to Gradle 32 | - License under COIL 33 | - Add ability to have non-public @Gauge methods anywhere in the type hierarchy of an injected type 34 | - Updated dependencies: Metrics 3.1.2, SLF4J 1.7.12, Guice 4.0 35 | - 3.1.3 36 | - Add support for `@Counted` 37 | - Move metric name creation into `MetricNamer` for easy customization 38 | - Move AdminServletModule into [metrics-guice-servlet](https://github.com/palominolabs/metrics-guice-servlet) 39 | - 3.1.2 40 | - Make injection listeners public 41 | - Depend on Metrics 3.1.0 42 | - Tweak metric naming to avoid duplicate names for different metrics 43 | - 3.1.1 44 | - Allow specifying a custom matcher 45 | - 3.1.0 46 | - Don't create MetricRegistry, HealthCheckRegistry, or JmxReporter for the user. This makes it easier to integrate with existing systems that already have instances that should be used. 47 | - Rename InstrumentationModule to MetricsInstrumentationModule 48 | - Update to SLF4J 49 | - 3.0.2 50 | - Update to Metrics 3.0.2 51 | - 3.0.1 52 | - Update to Jackson 2.0.2 53 | - Update to Metrics 3.0.1 54 | - Update to SLF4J 1.7.6 55 | - Use javax.servlet:javax.servlet-api for servlet implementation 56 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.time.Duration 2 | import java.net.URI 3 | 4 | plugins { 5 | `java-library` 6 | `maven-publish` 7 | signing 8 | id("biz.aQute.bnd.builder") version "5.3.0" 9 | id("net.researchgate.release") version "2.8.1" 10 | id("io.github.gradle-nexus.publish-plugin") version "1.1.0" 11 | id("com.github.ben-manes.versions") version "0.38.0" 12 | id("ru.vyarus.animalsniffer") version "1.5.3" 13 | } 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | group = "com.palominolabs.metrics" 20 | 21 | val deps by extra { 22 | mapOf( 23 | "metrics" to "5.0.0", 24 | "slf4j" to "1.7.30", 25 | "guice" to "4.2.3" 26 | ) 27 | } 28 | 29 | dependencies { 30 | implementation("io.dropwizard.metrics5:metrics-core:${deps["metrics"]}") 31 | implementation("io.dropwizard.metrics5:metrics-annotation:${deps["metrics"]}") 32 | implementation("com.google.inject:guice:${deps["guice"]}") 33 | implementation("com.google.code.findbugs:jsr305:3.0.2") 34 | 35 | testRuntimeOnly("org.slf4j:slf4j-simple:${deps["slf4j"]}") 36 | testRuntimeOnly("org.slf4j:jul-to-slf4j:${deps["slf4j"]}") 37 | testRuntimeOnly("org.slf4j:log4j-over-slf4j:${deps["slf4j"]}") 38 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0") 39 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0") 40 | testImplementation("org.hamcrest:hamcrest-all:1.3") 41 | 42 | signature("org.codehaus.mojo.signature:java18:1.0@signature") 43 | } 44 | 45 | java { 46 | sourceCompatibility = JavaVersion.VERSION_1_8 47 | targetCompatibility = JavaVersion.VERSION_1_8 48 | withSourcesJar() 49 | withJavadocJar() 50 | } 51 | 52 | tasks.withType { 53 | options.compilerArgs.add("-Xlint:unchecked") 54 | options.isDeprecation = true 55 | options.encoding = "UTF-8" 56 | } 57 | 58 | tasks { 59 | test { 60 | useJUnitPlatform() 61 | } 62 | 63 | afterReleaseBuild { 64 | dependsOn(provider { project.tasks.named("publishToSonatype") }) 65 | } 66 | } 67 | 68 | publishing { 69 | publications { 70 | register("sonatype") { 71 | from(components["java"]) 72 | // sonatype required pom elements 73 | pom { 74 | name.set("${project.group}:${project.name}") 75 | description.set(name) 76 | url.set("https://github.com/palominolabs/metrics-guice") 77 | licenses { 78 | license { 79 | name.set("Copyfree Open Innovation License 0.4") 80 | url.set("https://copyfree.org/content/standard/licenses/coil/license.txt") 81 | } 82 | } 83 | developers { 84 | developer { 85 | id.set("marshallpierce") 86 | name.set("Marshall Pierce") 87 | email.set("575695+marshallpierce@users.noreply.github.com") 88 | } 89 | } 90 | scm { 91 | connection.set("scm:git:https://github.com/palominolabs/metrics-guice") 92 | developerConnection.set("scm:git:ssh://git@github.com:palominolabs/metrics-guice.git") 93 | url.set("https://github.com/palominolabs/metrics-guice") 94 | } 95 | } 96 | } 97 | } 98 | 99 | // A safe throw-away place to publish to: 100 | // ./gradlew publishSonatypePublicationToLocalDebugRepository -Pversion=foo 101 | repositories { 102 | maven { 103 | name = "localDebug" 104 | url = URI.create("file:///${project.buildDir}/repos/localDebug") 105 | } 106 | } 107 | } 108 | 109 | // don't barf for devs without signing set up 110 | if (project.hasProperty("signing.keyId")) { 111 | signing { 112 | sign(project.extensions.getByType().publications["sonatype"]) 113 | } 114 | } 115 | 116 | nexusPublishing { 117 | repositories { 118 | sonatype { 119 | // sonatypeUsername and sonatypePassword properties are used automatically 120 | stagingProfileId.set("26c8b7fff47581") // com.palominolabs 121 | } 122 | } 123 | // these are not strictly required. The default timeouts are set to 1 minute. But Sonatype can be really slow. 124 | // If you get the error "java.net.SocketTimeoutException: timeout", these lines will help. 125 | connectTimeout.set(Duration.ofMinutes(3)) 126 | clientTimeout.set(Duration.ofMinutes(3)) 127 | } 128 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version = 5.0.2-SNAPSHOT 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palominolabs/metrics-guice/a7fc18f359f9d55963b321aff782f8008e11f692/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'metrics-guice' -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/CountedInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Counter; 4 | import io.dropwizard.metrics5.annotation.Counted; 5 | import org.aopalliance.intercept.MethodInterceptor; 6 | import org.aopalliance.intercept.MethodInvocation; 7 | 8 | class CountedInterceptor implements MethodInterceptor { 9 | 10 | private final Counter counter; 11 | private final boolean decrementAfterMethod; 12 | 13 | CountedInterceptor(Counter counter, Counted annotation) { 14 | this.counter = counter; 15 | decrementAfterMethod = !annotation.monotonic(); 16 | } 17 | 18 | @Override 19 | public Object invoke(MethodInvocation invocation) throws Throwable { 20 | counter.inc(); 21 | try { 22 | return invocation.proceed(); 23 | } finally { 24 | if (decrementAfterMethod) { 25 | counter.dec(); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/CountedListener.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Counter; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import io.dropwizard.metrics5.annotation.Counted; 6 | import com.palominolabs.metrics.guice.annotation.AnnotationResolver; 7 | import java.lang.reflect.Method; 8 | import javax.annotation.Nullable; 9 | import org.aopalliance.intercept.MethodInterceptor; 10 | 11 | /** 12 | * A listener which adds method interceptors to counted methods. 13 | */ 14 | public class CountedListener extends DeclaredMethodsTypeListener { 15 | private final MetricRegistry metricRegistry; 16 | private final MetricNamer metricNamer; 17 | private final AnnotationResolver annotationResolver; 18 | 19 | public CountedListener(MetricRegistry metricRegistry, MetricNamer metricNamer, 20 | AnnotationResolver annotationResolver) { 21 | this.metricRegistry = metricRegistry; 22 | this.metricNamer = metricNamer; 23 | this.annotationResolver = annotationResolver; 24 | } 25 | 26 | @Nullable 27 | @Override 28 | protected MethodInterceptor getInterceptor(Method method) { 29 | final Counted annotation = annotationResolver.findAnnotation(Counted.class, method); 30 | if (annotation != null) { 31 | final Counter counter = metricRegistry.counter(metricNamer.getNameForCounted(method, annotation)); 32 | return new CountedInterceptor(counter, annotation); 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/DeclaredMethodsTypeListener.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import com.google.inject.TypeLiteral; 4 | import com.google.inject.matcher.Matchers; 5 | import com.google.inject.spi.TypeEncounter; 6 | import com.google.inject.spi.TypeListener; 7 | import java.lang.reflect.Method; 8 | import javax.annotation.Nullable; 9 | import org.aopalliance.intercept.MethodInterceptor; 10 | 11 | /** 12 | * A TypeListener which delegates to {@link DeclaredMethodsTypeListener#getInterceptor(Method)} for each method in the 13 | * class's declared methods. 14 | */ 15 | abstract class DeclaredMethodsTypeListener implements TypeListener { 16 | 17 | @Override 18 | public void hear(TypeLiteral literal, TypeEncounter encounter) { 19 | Class klass = literal.getRawType(); 20 | 21 | for (Method method : klass.getDeclaredMethods()) { 22 | if (method.isSynthetic()) { 23 | continue; 24 | } 25 | 26 | final MethodInterceptor interceptor = getInterceptor(method); 27 | if (interceptor != null) { 28 | encounter.bindInterceptor(Matchers.only(method), interceptor); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Called for every method on every class in the type hierarchy of the visited type 35 | * 36 | * @param method method to get interceptor for 37 | * @return null if no interceptor should be applied, else an interceptor 38 | */ 39 | @Nullable 40 | protected abstract MethodInterceptor getInterceptor(Method method); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/DeclaringClassMetricNamer.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.MetricName; 4 | import io.dropwizard.metrics5.annotation.Counted; 5 | import io.dropwizard.metrics5.annotation.ExceptionMetered; 6 | import io.dropwizard.metrics5.annotation.Gauge; 7 | import io.dropwizard.metrics5.annotation.Metered; 8 | import io.dropwizard.metrics5.annotation.Timed; 9 | import java.lang.reflect.Method; 10 | import javax.annotation.Nonnull; 11 | 12 | import static io.dropwizard.metrics5.MetricRegistry.name; 13 | 14 | /** 15 | * Uses the name fields in the metric annotations, if present, or the method declaring class and method name. 16 | */ 17 | public class DeclaringClassMetricNamer implements MetricNamer { 18 | static final String COUNTER_SUFFIX = "counter"; 19 | static final String COUNTER_SUFFIX_MONOTONIC = "current"; 20 | static final String GAUGE_SUFFIX = "gauge"; 21 | static final String METERED_SUFFIX = "meter"; 22 | static final String TIMED_SUFFIX = "timer"; 23 | 24 | @Nonnull 25 | @Override 26 | public MetricName getNameForCounted(@Nonnull Method method, @Nonnull Counted counted) { 27 | if (counted.absolute()) { 28 | return name(counted.name()); 29 | } 30 | 31 | if (counted.name().isEmpty()) { 32 | if (counted.monotonic()) { 33 | return name(method.getDeclaringClass(), method.getName(), COUNTER_SUFFIX_MONOTONIC); 34 | } else { 35 | return name(method.getDeclaringClass(), method.getName(), COUNTER_SUFFIX); 36 | } 37 | } 38 | 39 | return name(method.getDeclaringClass(), counted.name()); 40 | } 41 | 42 | @Nonnull 43 | @Override 44 | public MetricName getNameForExceptionMetered(@Nonnull Method method, @Nonnull ExceptionMetered exceptionMetered) { 45 | if (exceptionMetered.absolute()) { 46 | return name(exceptionMetered.name()); 47 | } 48 | 49 | if (exceptionMetered.name().isEmpty()) { 50 | return 51 | name(method.getDeclaringClass(), method.getName(), ExceptionMetered.DEFAULT_NAME_SUFFIX); 52 | } 53 | 54 | return name(method.getDeclaringClass(), exceptionMetered.name()); 55 | } 56 | 57 | @Nonnull 58 | @Override 59 | public MetricName getNameForGauge(@Nonnull Class instanceClass, @Nonnull Method method, @Nonnull Gauge gauge) { 60 | if (gauge.absolute()) { 61 | return name(gauge.name()); 62 | } 63 | 64 | if (gauge.name().isEmpty()) { 65 | return name(method.getDeclaringClass(), method.getName(), GAUGE_SUFFIX); 66 | } 67 | 68 | return name(method.getDeclaringClass(), gauge.name()); 69 | } 70 | 71 | @Nonnull 72 | @Override 73 | public MetricName getNameForMetered(@Nonnull Method method, @Nonnull Metered metered) { 74 | if (metered.absolute()) { 75 | return name(metered.name()); 76 | } 77 | 78 | if (metered.name().isEmpty()) { 79 | return name(method.getDeclaringClass(), method.getName(), METERED_SUFFIX); 80 | } 81 | 82 | return name(method.getDeclaringClass(), metered.name()); 83 | } 84 | 85 | @Nonnull 86 | @Override 87 | public MetricName getNameForTimed(@Nonnull Method method, @Nonnull Timed timed) { 88 | if (timed.absolute()) { 89 | return name(timed.name()); 90 | } 91 | 92 | if (timed.name().isEmpty()) { 93 | return name(method.getDeclaringClass(), method.getName(), TIMED_SUFFIX); 94 | } 95 | 96 | return name(method.getDeclaringClass(), timed.name()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/ExceptionMeteredInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Meter; 4 | import org.aopalliance.intercept.MethodInterceptor; 5 | import org.aopalliance.intercept.MethodInvocation; 6 | 7 | /** 8 | * A method interceptor which measures the rate at which the annotated method throws exceptions of a given type. 9 | */ 10 | class ExceptionMeteredInterceptor implements MethodInterceptor { 11 | 12 | private final Meter meter; 13 | private final Class klass; 14 | 15 | ExceptionMeteredInterceptor(Meter meter, Class klass) { 16 | this.meter = meter; 17 | this.klass = klass; 18 | } 19 | 20 | @Override 21 | public Object invoke(MethodInvocation invocation) throws Throwable { 22 | try { 23 | return invocation.proceed(); 24 | } catch (Throwable t) { 25 | if (klass.isAssignableFrom(t.getClass())) { 26 | meter.mark(); 27 | } 28 | throw t; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/ExceptionMeteredListener.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Meter; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import io.dropwizard.metrics5.annotation.ExceptionMetered; 6 | import com.palominolabs.metrics.guice.annotation.AnnotationResolver; 7 | import java.lang.reflect.Method; 8 | import javax.annotation.Nullable; 9 | import org.aopalliance.intercept.MethodInterceptor; 10 | 11 | /** 12 | * A listener which adds method interceptors to methods that should be instrumented for exceptions 13 | */ 14 | public class ExceptionMeteredListener extends DeclaredMethodsTypeListener { 15 | private final MetricRegistry metricRegistry; 16 | private final MetricNamer metricNamer; 17 | private final AnnotationResolver annotationResolver; 18 | 19 | public ExceptionMeteredListener(MetricRegistry metricRegistry, MetricNamer metricNamer, 20 | final AnnotationResolver annotationResolver) { 21 | this.metricRegistry = metricRegistry; 22 | this.metricNamer = metricNamer; 23 | this.annotationResolver = annotationResolver; 24 | } 25 | 26 | @Nullable 27 | @Override 28 | protected MethodInterceptor getInterceptor(Method method) { 29 | final ExceptionMetered annotation = annotationResolver.findAnnotation(ExceptionMetered.class, method); 30 | if (annotation != null) { 31 | final Meter meter = metricRegistry.meter(metricNamer.getNameForExceptionMetered(method, annotation)); 32 | return new ExceptionMeteredInterceptor(meter, annotation.cause()); 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/GaugeInjectionListener.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Gauge; 4 | import io.dropwizard.metrics5.MetricName; 5 | import io.dropwizard.metrics5.MetricRegistry; 6 | import com.google.inject.spi.InjectionListener; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * An injection listener which creates a gauge for the declaring class with the given name (or the method's name, if 12 | * none was provided) which returns the value returned by the annotated method. 13 | */ 14 | public class GaugeInjectionListener implements InjectionListener { 15 | private final MetricRegistry metricRegistry; 16 | private final MetricName metricName; 17 | private final Method method; 18 | 19 | public GaugeInjectionListener(MetricRegistry metricRegistry, MetricName metricName, Method method) { 20 | this.metricRegistry = metricRegistry; 21 | this.metricName = metricName; 22 | this.method = method; 23 | } 24 | 25 | @Override 26 | public void afterInjection(final I i) { 27 | metricRegistry.register(metricName, (Gauge) () -> { 28 | try { 29 | return method.invoke(i); 30 | } catch (Exception e) { 31 | return new RuntimeException(e); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/GaugeInstanceClassMetricNamer.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.MetricName; 4 | import io.dropwizard.metrics5.annotation.Gauge; 5 | import java.lang.reflect.Method; 6 | import javax.annotation.Nonnull; 7 | 8 | import static io.dropwizard.metrics5.MetricRegistry.name; 9 | 10 | /** 11 | * For gauges (which can reside in superclasses of the type being instantiated), this will use the instantiated type for 12 | * the resulting metric name no matter what superclass declares the gauge method. This allows for a gauge located in a 13 | * superclass to be used by multiple inheritors without causing a duplicate metric name clash. 14 | * 15 | * For other metric types, which are not available on superclass methods, the declaring class (which would be the same 16 | * as the instantiated class) is used, as in {@link DeclaringClassMetricNamer}. 17 | */ 18 | public class GaugeInstanceClassMetricNamer extends DeclaringClassMetricNamer { 19 | @Nonnull 20 | @Override 21 | public MetricName getNameForGauge(@Nonnull Class instanceClass, @Nonnull Method method, @Nonnull Gauge gauge) { 22 | if (gauge.absolute()) { 23 | return name(gauge.name()); 24 | } 25 | 26 | if (gauge.name().isEmpty()) { 27 | return name(instanceClass, method.getName(), GAUGE_SUFFIX); 28 | } 29 | 30 | return name(instanceClass, gauge.name()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/GaugeListener.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.MetricName; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import io.dropwizard.metrics5.annotation.Gauge; 6 | import com.google.inject.TypeLiteral; 7 | import com.google.inject.spi.TypeEncounter; 8 | import com.google.inject.spi.TypeListener; 9 | import com.palominolabs.metrics.guice.annotation.AnnotationResolver; 10 | import java.lang.reflect.Method; 11 | 12 | /** 13 | * A listener which adds gauge injection listeners to classes with gauges. 14 | */ 15 | public class GaugeListener implements TypeListener { 16 | private final MetricRegistry metricRegistry; 17 | private final MetricNamer metricNamer; 18 | private final AnnotationResolver annotationResolver; 19 | 20 | public GaugeListener(MetricRegistry metricRegistry, MetricNamer metricNamer, 21 | final AnnotationResolver annotationResolver) { 22 | this.metricRegistry = metricRegistry; 23 | this.metricNamer = metricNamer; 24 | this.annotationResolver = annotationResolver; 25 | } 26 | 27 | @Override 28 | public void hear(final TypeLiteral literal, TypeEncounter encounter) { 29 | Class klass = literal.getRawType(); 30 | final Class instanceType = klass; 31 | 32 | do { 33 | for (Method method : klass.getDeclaredMethods()) { 34 | if (method.isSynthetic()) { 35 | continue; 36 | } 37 | 38 | final Gauge annotation = annotationResolver.findAnnotation(Gauge.class, method); 39 | if (annotation != null) { 40 | if (method.getParameterTypes().length == 0) { 41 | final MetricName metricName = metricNamer.getNameForGauge(instanceType, method, annotation); 42 | 43 | // deprecated method in java 9, but replacement is not available in java 8 44 | if (!method.isAccessible()) { 45 | method.setAccessible(true); 46 | } 47 | 48 | encounter.register(new GaugeInjectionListener<>(metricRegistry, metricName, method)); 49 | } else { 50 | encounter.addError("Method %s is annotated with @Gauge but requires parameters.", 51 | method); 52 | } 53 | } 54 | } 55 | } while ((klass = klass.getSuperclass()) != null); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/MeteredInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Meter; 4 | import org.aopalliance.intercept.MethodInterceptor; 5 | import org.aopalliance.intercept.MethodInvocation; 6 | 7 | /** 8 | * A method interceptor which creates a meter for the declaring class with the given name (or the method's name, if none 9 | * was provided), and which measures the rate at which the annotated method is invoked. 10 | */ 11 | class MeteredInterceptor implements MethodInterceptor { 12 | 13 | private final Meter meter; 14 | 15 | MeteredInterceptor(Meter meter) { 16 | this.meter = meter; 17 | } 18 | 19 | @Override 20 | public Object invoke(MethodInvocation invocation) throws Throwable { 21 | meter.mark(); 22 | return invocation.proceed(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/MeteredListener.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Meter; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import io.dropwizard.metrics5.annotation.Metered; 6 | import com.palominolabs.metrics.guice.annotation.AnnotationResolver; 7 | import java.lang.reflect.Method; 8 | import javax.annotation.Nullable; 9 | import org.aopalliance.intercept.MethodInterceptor; 10 | 11 | /** 12 | * A listener which adds method interceptors to metered methods. 13 | */ 14 | public class MeteredListener extends DeclaredMethodsTypeListener { 15 | private final MetricRegistry metricRegistry; 16 | private final MetricNamer metricNamer; 17 | private final AnnotationResolver annotationResolver; 18 | 19 | public MeteredListener(MetricRegistry metricRegistry, MetricNamer metricNamer, 20 | AnnotationResolver annotationResolver) { 21 | this.metricRegistry = metricRegistry; 22 | this.metricNamer = metricNamer; 23 | this.annotationResolver = annotationResolver; 24 | } 25 | 26 | @Nullable 27 | @Override 28 | protected MethodInterceptor getInterceptor(Method method) { 29 | final Metered annotation = annotationResolver.findAnnotation(Metered.class, method); 30 | if (annotation != null) { 31 | final Meter meter = metricRegistry.meter(metricNamer.getNameForMetered(method, annotation)); 32 | return new MeteredInterceptor(meter); 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/MetricNamer.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.MetricName; 4 | import io.dropwizard.metrics5.annotation.Counted; 5 | import io.dropwizard.metrics5.annotation.ExceptionMetered; 6 | import io.dropwizard.metrics5.annotation.Gauge; 7 | import io.dropwizard.metrics5.annotation.Metered; 8 | import io.dropwizard.metrics5.annotation.Timed; 9 | 10 | import javax.annotation.Nonnull; 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | * Generates for the metrics corresponding to the various metric annotations. 15 | */ 16 | public interface MetricNamer { 17 | 18 | @Nonnull 19 | MetricName getNameForCounted(@Nonnull Method method, @Nonnull Counted counted); 20 | 21 | @Nonnull 22 | MetricName getNameForExceptionMetered(@Nonnull Method method, @Nonnull ExceptionMetered exceptionMetered); 23 | 24 | /** 25 | * For AOP-wrapped method invocations (which is how all metrics other than Gauges have to be handled), there isn't a 26 | * way to handle annotated methods defined in superclasses since we can't AOP superclass methods. Gauges, however, 27 | * are invoked without requiring AOP, so gauges from superclasses are available. 28 | * 29 | * @param instanceClass the type being instantiated 30 | * @param method the annotated method (which may belong to a supertype) 31 | * @param gauge the annotation 32 | * @return a name 33 | */ 34 | @Nonnull 35 | MetricName getNameForGauge(@Nonnull Class instanceClass, @Nonnull Method method, @Nonnull Gauge gauge); 36 | 37 | @Nonnull 38 | MetricName getNameForMetered(@Nonnull Method method, @Nonnull Metered metered); 39 | 40 | @Nonnull 41 | MetricName getNameForTimed(@Nonnull Method method, @Nonnull Timed timed); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/MetricsInstrumentationModule.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.MetricRegistry; 4 | import io.dropwizard.metrics5.annotation.Counted; 5 | import io.dropwizard.metrics5.annotation.ExceptionMetered; 6 | import io.dropwizard.metrics5.annotation.Gauge; 7 | import io.dropwizard.metrics5.annotation.Metered; 8 | import io.dropwizard.metrics5.annotation.Timed; 9 | import com.google.common.base.Preconditions; 10 | import com.google.inject.AbstractModule; 11 | import com.google.inject.TypeLiteral; 12 | import com.google.inject.matcher.Matcher; 13 | import com.google.inject.matcher.Matchers; 14 | import com.palominolabs.metrics.guice.annotation.AnnotationResolver; 15 | import com.palominolabs.metrics.guice.annotation.MethodAnnotationResolver; 16 | import javax.annotation.Nonnull; 17 | 18 | /** 19 | * A Guice module which instruments methods annotated with the {@link Metered}, {@link Timed}, {@link Gauge}, {@link 20 | * Counted}, and {@link ExceptionMetered} annotations. 21 | * 22 | * @see Gauge 23 | * @see Metered 24 | * @see Timed 25 | * @see ExceptionMetered 26 | * @see Counted 27 | * @see MeteredInterceptor 28 | * @see TimedInterceptor 29 | * @see GaugeInjectionListener 30 | */ 31 | public class MetricsInstrumentationModule extends AbstractModule { 32 | private final MetricRegistry metricRegistry; 33 | private final Matcher> matcher; 34 | private final MetricNamer metricNamer; 35 | private final AnnotationResolver annotationResolver; 36 | 37 | public static Builder builder() { 38 | return new Builder(); 39 | } 40 | 41 | /** 42 | * @param metricRegistry The registry to use when creating meters, etc. for annotated methods. 43 | * @param matcher The matcher to determine which types to look for metrics in 44 | * @param metricNamer The metric namer to use when creating names for metrics for annotated methods 45 | * @param annotationResolver The annotation provider 46 | */ 47 | private MetricsInstrumentationModule(MetricRegistry metricRegistry, Matcher> matcher, 48 | MetricNamer metricNamer, AnnotationResolver annotationResolver) { 49 | this.metricRegistry = metricRegistry; 50 | this.matcher = matcher; 51 | this.metricNamer = metricNamer; 52 | this.annotationResolver = annotationResolver; 53 | } 54 | 55 | @Override 56 | protected void configure() { 57 | bindListener(matcher, new MeteredListener(metricRegistry, metricNamer, annotationResolver)); 58 | bindListener(matcher, new TimedListener(metricRegistry, metricNamer, annotationResolver)); 59 | bindListener(matcher, new GaugeListener(metricRegistry, metricNamer, annotationResolver)); 60 | bindListener(matcher, new ExceptionMeteredListener(metricRegistry, metricNamer, annotationResolver)); 61 | bindListener(matcher, new CountedListener(metricRegistry, metricNamer, annotationResolver)); 62 | } 63 | 64 | public static class Builder { 65 | private MetricRegistry metricRegistry; 66 | private Matcher> matcher = Matchers.any(); 67 | private MetricNamer metricNamer = new GaugeInstanceClassMetricNamer(); 68 | private AnnotationResolver annotationResolver = new MethodAnnotationResolver(); 69 | 70 | /** 71 | * @param metricRegistry The registry to use when creating meters, etc. for annotated methods. 72 | * @return this 73 | */ 74 | @Nonnull 75 | public Builder withMetricRegistry(@Nonnull MetricRegistry metricRegistry) { 76 | this.metricRegistry = metricRegistry; 77 | 78 | return this; 79 | } 80 | 81 | /** 82 | * @param matcher The matcher to determine which types to look for metrics in 83 | * @return this 84 | */ 85 | @Nonnull 86 | public Builder withMatcher(@Nonnull Matcher> matcher) { 87 | this.matcher = matcher; 88 | 89 | return this; 90 | } 91 | 92 | /** 93 | * @param metricNamer The metric namer to use when creating names for metrics for annotated methods 94 | * @return this 95 | */ 96 | @Nonnull 97 | public Builder withMetricNamer(@Nonnull MetricNamer metricNamer) { 98 | this.metricNamer = metricNamer; 99 | 100 | return this; 101 | } 102 | 103 | /** 104 | * @param annotationResolver Annotation resolver to use 105 | * @return this 106 | */ 107 | @Nonnull 108 | public Builder withAnnotationMatcher(@Nonnull AnnotationResolver annotationResolver) { 109 | this.annotationResolver = annotationResolver; 110 | 111 | return this; 112 | } 113 | 114 | @Nonnull 115 | public MetricsInstrumentationModule build() { 116 | return new MetricsInstrumentationModule( 117 | Preconditions.checkNotNull(metricRegistry), 118 | Preconditions.checkNotNull(matcher), 119 | Preconditions.checkNotNull(metricNamer), 120 | Preconditions.checkNotNull(annotationResolver) 121 | ); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/TimedInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Timer; 4 | import org.aopalliance.intercept.MethodInterceptor; 5 | import org.aopalliance.intercept.MethodInvocation; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * A method interceptor which times the execution of the annotated method. 11 | */ 12 | class TimedInterceptor implements MethodInterceptor { 13 | 14 | private final Timer timer; 15 | 16 | TimedInterceptor(Timer timer) { 17 | this.timer = timer; 18 | } 19 | 20 | @Override 21 | public Object invoke(MethodInvocation invocation) throws Throwable { 22 | // Since these timers are always created via the default ctor (via MetricRegister#timer), they always use 23 | // nanoTime, so we can save an allocation here by not using Context. 24 | long start = System.nanoTime(); 25 | try { 26 | return invocation.proceed(); 27 | } finally { 28 | timer.update(System.nanoTime() - start, TimeUnit.NANOSECONDS); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/TimedListener.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.MetricRegistry; 4 | import io.dropwizard.metrics5.Timer; 5 | import io.dropwizard.metrics5.annotation.Timed; 6 | import com.palominolabs.metrics.guice.annotation.AnnotationResolver; 7 | import java.lang.reflect.Method; 8 | import javax.annotation.Nullable; 9 | import org.aopalliance.intercept.MethodInterceptor; 10 | 11 | /** 12 | * A listener which adds method interceptors to timed methods. 13 | */ 14 | public class TimedListener extends DeclaredMethodsTypeListener { 15 | private final MetricRegistry metricRegistry; 16 | private final MetricNamer metricNamer; 17 | private final AnnotationResolver annotationResolver; 18 | 19 | public TimedListener(MetricRegistry metricRegistry, MetricNamer metricNamer, 20 | final AnnotationResolver annotationResolver) { 21 | this.metricRegistry = metricRegistry; 22 | this.metricNamer = metricNamer; 23 | this.annotationResolver = annotationResolver; 24 | } 25 | 26 | @Nullable 27 | @Override 28 | protected MethodInterceptor getInterceptor(Method method) { 29 | final Timed annotation = annotationResolver.findAnnotation(Timed.class, method); 30 | if (annotation != null) { 31 | final Timer timer = metricRegistry.timer(metricNamer.getNameForTimed(method, annotation)); 32 | return new TimedInterceptor(timer); 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/annotation/AnnotationResolver.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice.annotation; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * Finds annotations, if any, pertaining to a particular Method. Extension point for customizing annotation lookup. 10 | */ 11 | public interface AnnotationResolver { 12 | /** 13 | * @param annotationClass Metrics annotation to look for 14 | * @param method method that the corresponding metric may be applied to 15 | * @param annotation type 16 | * @return a T instance, if found, else null 17 | */ 18 | @Nullable 19 | T findAnnotation(@Nonnull Class annotationClass, @Nonnull Method method); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/annotation/ClassAnnotationResolver.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice.annotation; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * Looks for annotations on the enclosing class of the method. 10 | */ 11 | public class ClassAnnotationResolver implements AnnotationResolver { 12 | @Override 13 | @Nullable 14 | public T findAnnotation(@Nonnull final Class annotationClass, 15 | @Nonnull final Method method) { 16 | return method.getDeclaringClass().getAnnotation(annotationClass); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/annotation/ListAnnotationResolver.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice.annotation; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import java.util.List; 6 | import javax.annotation.Nonnull; 7 | import javax.annotation.Nullable; 8 | 9 | /** 10 | * Delegates to the provided list of resolvers, applying each resolver in turn. 11 | */ 12 | public class ListAnnotationResolver implements AnnotationResolver { 13 | private final List resolvers; 14 | 15 | public ListAnnotationResolver(List resolvers) { 16 | this.resolvers = resolvers; 17 | } 18 | 19 | @Nullable 20 | @Override 21 | public T findAnnotation(@Nonnull Class annotationClass, @Nonnull Method method) { 22 | for (AnnotationResolver resolver : resolvers) { 23 | T result = resolver.findAnnotation(annotationClass, method); 24 | if (result != null) { 25 | return result; 26 | } 27 | } 28 | 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/palominolabs/metrics/guice/annotation/MethodAnnotationResolver.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice.annotation; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * Looks for annotations on the method itself. 10 | */ 11 | public class MethodAnnotationResolver implements AnnotationResolver { 12 | 13 | @Override 14 | @Nullable 15 | public T findAnnotation(@Nonnull final Class annotationClass, 16 | @Nonnull final Method method) { 17 | return method.getAnnotation(annotationClass); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/CountInvocationGenericSubtypeTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Counter; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import com.google.inject.Guice; 6 | import com.google.inject.Injector; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static io.dropwizard.metrics5.MetricRegistry.name; 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | 14 | class CountInvocationGenericSubtypeTest { 15 | 16 | private GenericThing instance; 17 | private MetricRegistry registry; 18 | 19 | @BeforeEach 20 | void setup() { 21 | this.registry = new MetricRegistry(); 22 | final Injector injector = Guice.createInjector(MetricsInstrumentationModule.builder().withMetricRegistry(registry).build()); 23 | this.instance = injector.getInstance(StringThing.class); 24 | } 25 | 26 | @Test 27 | void testCountsInvocationOfGenericOverride() { 28 | instance.doThing("foo"); 29 | 30 | final Counter metric = registry.getCounters().get(name("stringThing")); 31 | 32 | assertNotNull(metric); 33 | 34 | assertEquals(1, metric.getCount()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/CountedTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Counter; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import com.google.inject.Guice; 6 | import com.google.inject.Injector; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static io.dropwizard.metrics5.MetricRegistry.name; 11 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.COUNTER_SUFFIX; 12 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.COUNTER_SUFFIX_MONOTONIC; 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | import static org.hamcrest.Matchers.is; 15 | import static org.hamcrest.Matchers.notNullValue; 16 | import static org.junit.jupiter.api.Assertions.assertNull; 17 | 18 | class CountedTest { 19 | private InstrumentedWithCounter instance; 20 | private MetricRegistry registry; 21 | 22 | @BeforeEach 23 | void setup() { 24 | this.registry = new MetricRegistry(); 25 | final Injector injector = Guice.createInjector(MetricsInstrumentationModule.builder().withMetricRegistry(registry).build()); 26 | this.instance = injector.getInstance(InstrumentedWithCounter.class); 27 | } 28 | 29 | @Test 30 | void aCounterAnnotatedMethod() { 31 | instance.doAThing(); 32 | 33 | final Counter metric = registry.getCounters().get(name(InstrumentedWithCounter.class, "things")); 34 | 35 | assertThat("Guice creates a metric", 36 | metric, 37 | is(notNullValue())); 38 | 39 | assertThat("Guice creates a counter with the given value", 40 | metric.getCount(), 41 | is((long) 1)); 42 | } 43 | 44 | @Test 45 | void aCounterAnnotatedMethodWithDefaultName() { 46 | instance.doAnotherThing(); 47 | 48 | final Counter metric = registry.getCounters().get(name(InstrumentedWithCounter.class, 49 | "doAnotherThing", COUNTER_SUFFIX_MONOTONIC)); 50 | 51 | assertThat("Guice creates a metric", 52 | metric, 53 | is(notNullValue())); 54 | 55 | assertThat("Guice creates a counter with the given value", 56 | metric.getCount(), 57 | is((long) 1)); 58 | } 59 | 60 | @Test 61 | void aCounterAnnotatedMethodWithDefaultNameAndMonotonicFalse() { 62 | instance.doYetAnotherThing(); 63 | 64 | final Counter metric = registry.getCounters().get(name(InstrumentedWithCounter.class, 65 | "doYetAnotherThing", COUNTER_SUFFIX)); 66 | 67 | assertThat("Guice creates a metric", 68 | metric, 69 | is(notNullValue())); 70 | 71 | // if things are working well then this should still be zero... 72 | assertThat("Guice creates a counter with the given value", 73 | metric.getCount(), 74 | is((long) 0)); 75 | } 76 | 77 | @Test 78 | void aCounterAnnotatedMethodWithAbsoluteName() { 79 | instance.doAThingWithAbsoluteName(); 80 | 81 | final Counter metric = registry.getCounters().get(name("absoluteName")); 82 | 83 | assertThat("Guice creates a metric", 84 | metric, 85 | is(notNullValue())); 86 | 87 | assertThat("Guice creates a counter with the given value", 88 | metric.getCount(), 89 | is((long) 1)); 90 | } 91 | 92 | /** 93 | * Test to document the current (correct but regrettable) behavior. 94 | * 95 | * Crawling the injected class's supertype hierarchy doesn't really accomplish anything because AOPing supertype 96 | * methods doesn't work. 97 | * 98 | * In certain cases (e.g. a public type that inherits a public method from a non-public supertype), a synthetic 99 | * bridge method is generated in the subtype that invokes the supertype method, and this does copy the annotations 100 | * of the supertype method. However, we can't allow intercepting synthetic bridge methods in general: when a subtype 101 | * overrides a generic supertype's method with a more specifically typed method that would not override the 102 | * type-erased supertype method, a bridge method matching the supertype's erased signature is generated, but with 103 | * the subtype's method's annotation. It's not OK to intercept that synthetic method because that would lead to 104 | * double-counting, etc, since we also would intercept the regular non-synthetic method. 105 | * 106 | * Thus, we cannot intercept synthetic methods to maintain correctness, so we also lose out on one small way that we 107 | * could intercept annotated methods in superclasses. 108 | */ 109 | @Test 110 | void aCounterForSuperclassMethod() { 111 | instance.counterParent(); 112 | 113 | final Counter metric = registry.getCounters().get(name("counterParent")); 114 | 115 | // won't be created because we don't bother looking for supertype methods 116 | assertNull(metric); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/DeclaringClassNamerGaugeTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Gauge; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.dropwizard.metrics5.MetricRegistry.name; 7 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.GAUGE_SUFFIX; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | 11 | class DeclaringClassNamerGaugeTest extends GaugeTestBase { 12 | @Override 13 | MetricNamer getMetricNamer() { 14 | return new DeclaringClassMetricNamer(); 15 | } 16 | 17 | @Test 18 | void aGaugeWithoutNameInSuperclass() { 19 | // named for the declaring class 20 | final Gauge metric = 21 | registry.getGauges().get(name(InstrumentedWithGaugeParent.class, "justAGaugeFromParent", 22 | GAUGE_SUFFIX)); 23 | 24 | assertNotNull(metric); 25 | assertEquals("justAGaugeFromParent", metric.getValue()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/ExceptionMeteredTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Meter; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import io.dropwizard.metrics5.Timer; 6 | import com.google.inject.Guice; 7 | import com.google.inject.Injector; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static io.dropwizard.metrics5.MetricRegistry.name; 12 | import static io.dropwizard.metrics5.annotation.ExceptionMetered.DEFAULT_NAME_SUFFIX; 13 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.METERED_SUFFIX; 14 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.TIMED_SUFFIX; 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.hamcrest.Matchers.instanceOf; 17 | import static org.hamcrest.Matchers.is; 18 | import static org.hamcrest.Matchers.notNullValue; 19 | import static org.junit.jupiter.api.Assertions.fail; 20 | 21 | class ExceptionMeteredTest { 22 | private InstrumentedWithExceptionMetered instance; 23 | private MetricRegistry registry; 24 | 25 | @BeforeEach 26 | void setup() { 27 | this.registry = new MetricRegistry(); 28 | final Injector injector = Guice.createInjector(MetricsInstrumentationModule.builder().withMetricRegistry(registry).build()); 29 | this.instance = injector.getInstance(InstrumentedWithExceptionMetered.class); 30 | } 31 | 32 | @Test 33 | void anExceptionMeteredAnnotatedMethodWithPublicScope() { 34 | 35 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, "exceptionCounter")); 36 | assertMetricIsSetup(metric); 37 | 38 | assertThat("Metric intialises to zero", 39 | metric.getCount(), 40 | is(0L)); 41 | 42 | try { 43 | instance.explodeWithPublicScope(true); 44 | fail("Expected an exception to be thrown"); 45 | } catch (RuntimeException e) { 46 | // Swallow the expected exception 47 | } 48 | 49 | assertThat("Metric is marked", 50 | metric.getCount(), 51 | is(1L)); 52 | } 53 | 54 | @Test 55 | void anExceptionMeteredAnnotatedMethod_WithNoMetricName() { 56 | 57 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 58 | "explodeForUnnamedMetric", DEFAULT_NAME_SUFFIX)); 59 | assertMetricIsSetup(metric); 60 | 61 | assertThat("Metric intialises to zero", 62 | metric.getCount(), 63 | is(0L)); 64 | 65 | try { 66 | instance.explodeForUnnamedMetric(); 67 | fail("Expected an exception to be thrown"); 68 | } catch (RuntimeException e) { 69 | // Swallow the expected exception 70 | } 71 | 72 | assertThat("Metric is marked", 73 | metric.getCount(), 74 | is(1L)); 75 | } 76 | 77 | @Test 78 | void anExceptionMeteredAnnotatedMethod_WithName() { 79 | 80 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, "n")); 81 | assertMetricIsSetup(metric); 82 | 83 | assertThat("Metric intialises to zero", 84 | metric.getCount(), 85 | is(0L)); 86 | 87 | try { 88 | instance.explodeForMetricWithName(); 89 | fail("Expected an exception to be thrown"); 90 | } catch (RuntimeException e) { 91 | // Swallow the expected exception 92 | } 93 | 94 | assertThat("Metric is marked", 95 | metric.getCount(), 96 | is(1L)); 97 | } 98 | 99 | @Test 100 | void anExceptionMeteredAnnotatedMethod_WithAbsoluteName() { 101 | 102 | final Meter metric = registry.getMeters().get(name("absoluteName")); 103 | assertMetricIsSetup(metric); 104 | 105 | assertThat("Metric intialises to zero", 106 | metric.getCount(), 107 | is(0L)); 108 | 109 | try { 110 | instance.explodeForMetricWithAbsoluteName(); 111 | fail("Expected an exception to be thrown"); 112 | } catch (RuntimeException e) { 113 | // Swallow the expected exception 114 | } 115 | 116 | assertThat("Metric is marked", 117 | metric.getCount(), 118 | is(1L)); 119 | } 120 | 121 | @Test 122 | void anExceptionMeteredAnnotatedMethod_WithPublicScopeButNoExceptionThrown() { 123 | 124 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 125 | "exceptionCounter")); 126 | assertMetricIsSetup(metric); 127 | 128 | assertThat("Metric intialises to zero", 129 | metric.getCount(), 130 | is(0L)); 131 | 132 | instance.explodeWithPublicScope(false); 133 | 134 | assertThat("Metric should remain at zero if no exception is thrown", 135 | metric.getCount(), 136 | is(0L)); 137 | } 138 | 139 | @Test 140 | void anExceptionMeteredAnnotatedMethod_WithDefaultScope() { 141 | 142 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 143 | "explodeWithDefaultScope", DEFAULT_NAME_SUFFIX)); 144 | assertMetricIsSetup(metric); 145 | 146 | assertThat("Metric intialises to zero", 147 | metric.getCount(), 148 | is(0L)); 149 | 150 | try { 151 | instance.explodeWithDefaultScope(); 152 | fail("Expected an exception to be thrown"); 153 | } catch (RuntimeException ignored) { 154 | } 155 | 156 | assertThat("Metric is marked", 157 | metric.getCount(), 158 | is(1L)); 159 | } 160 | 161 | @Test 162 | void anExceptionMeteredAnnotatedMethod_WithProtectedScope() { 163 | 164 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 165 | "explodeWithProtectedScope", DEFAULT_NAME_SUFFIX)); 166 | 167 | assertMetricIsSetup(metric); 168 | 169 | assertThat("Metric intialises to zero", 170 | metric.getCount(), 171 | is(0L)); 172 | 173 | try { 174 | instance.explodeWithProtectedScope(); 175 | fail("Expected an exception to be thrown"); 176 | } catch (RuntimeException ignored) { 177 | } 178 | 179 | assertThat("Metric is marked", 180 | metric.getCount(), 181 | is(1L)); 182 | } 183 | 184 | @Test 185 | void anExceptionMeteredAnnotatedMethod_WithPublicScope_AndSpecificTypeOfException() { 186 | 187 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 188 | "failures")); 189 | assertMetricIsSetup(metric); 190 | 191 | assertThat("Metric intialises to zero", 192 | metric.getCount(), 193 | is(0L)); 194 | try { 195 | instance.errorProneMethod(new MyException()); 196 | fail("Expected an exception to be thrown"); 197 | } catch (MyException ignored) { 198 | } 199 | 200 | assertThat("Metric should be marked when the specified exception type is thrown", 201 | metric.getCount(), 202 | is(1L)); 203 | } 204 | 205 | @Test 206 | void anExceptionMeteredAnnotatedMethod_WithPublicScope_AndSubclassesOfSpecifiedException() { 207 | 208 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 209 | "failures")); 210 | assertMetricIsSetup(metric); 211 | 212 | assertThat("Metric intialises to zero", 213 | metric.getCount(), 214 | is(0L)); 215 | try { 216 | instance.errorProneMethod(new MySpecialisedException()); 217 | fail("Expected an exception to be thrown"); 218 | } catch (MyException ignored) { 219 | } 220 | 221 | assertThat( 222 | "Metric should be marked when a subclass of the specified exception type is thrown", 223 | metric.getCount(), 224 | is(1L)); 225 | } 226 | 227 | @Test 228 | void anExceptionMeteredAnnotatedMethod_WithPublicScope_ButDifferentTypeOfException() { 229 | 230 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 231 | "failures")); 232 | assertMetricIsSetup(metric); 233 | 234 | assertThat("Metric intialises to zero", 235 | metric.getCount(), 236 | is(0L)); 237 | try { 238 | instance.errorProneMethod(new MyOtherException()); 239 | fail("Expected an exception to be thrown"); 240 | } catch (MyOtherException ignored) { 241 | } 242 | 243 | assertThat("Metric should not be marked if the exception is a different type", 244 | metric.getCount(), 245 | is(0L)); 246 | } 247 | 248 | @Test 249 | void anExceptionMeteredAnnotatedMethod_WithExtraOptions() { 250 | 251 | try { 252 | instance.causeAnOutOfBoundsException(); 253 | } catch (ArrayIndexOutOfBoundsException ignored) { 254 | 255 | } 256 | 257 | final Meter metric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 258 | "things")); 259 | 260 | assertMetricIsSetup(metric); 261 | 262 | assertThat("Guice creates a meter which gets marked", 263 | metric.getCount(), 264 | is(1L)); 265 | } 266 | 267 | @Test 268 | void aMethodAnnotatedWithBothATimerAndAnExceptionCounter() { 269 | 270 | final Timer timedMetric = registry.getTimers().get(name(InstrumentedWithExceptionMetered.class, 271 | "timedAndException", TIMED_SUFFIX)); 272 | 273 | final Meter errorMetric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 274 | "timedAndException", DEFAULT_NAME_SUFFIX)); 275 | 276 | assertThat("Guice creates a metric", 277 | timedMetric, 278 | is(notNullValue())); 279 | 280 | assertThat("Guice creates a timer", 281 | timedMetric, 282 | is(instanceOf(Timer.class))); 283 | 284 | assertThat("Guice creates a metric", 285 | errorMetric, 286 | is(notNullValue())); 287 | 288 | assertThat("Guice creates a meter", 289 | errorMetric, 290 | is(instanceOf(Meter.class))); 291 | 292 | // Counts should start at zero 293 | assertThat("Timer Metric should be zero when initialised", 294 | timedMetric.getCount(), 295 | is(0L)); 296 | 297 | assertThat("Error Metric should be zero when initialised", 298 | errorMetric.getCount(), 299 | is(0L)); 300 | 301 | // Invoke, but don't throw an exception 302 | instance.timedAndException(null); 303 | 304 | assertThat("Expected the meter metric to be marked on invocation", 305 | timedMetric.getCount(), 306 | is(1L)); 307 | 308 | assertThat("Expected the exception metric to be zero since no exceptions thrown", 309 | errorMetric.getCount(), 310 | is(0L)); 311 | 312 | // Invoke and throw an exception 313 | try { 314 | instance.timedAndException(new RuntimeException()); 315 | fail("Should have thrown an exception"); 316 | } catch (Exception ignored) { 317 | } 318 | 319 | assertThat("Expected a count of 2, one for each invocation", 320 | timedMetric.getCount(), 321 | is(2L)); 322 | 323 | assertThat("Expected exception count to be 1 as one (of two) invocations threw an exception", 324 | errorMetric.getCount(), 325 | is(1L)); 326 | } 327 | 328 | @Test 329 | void aMethodAnnotatedWithBothAMeteredAndAnExceptionCounter() { 330 | 331 | final Meter meteredMetric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 332 | "meteredAndException", METERED_SUFFIX)); 333 | 334 | final Meter errorMetric = registry.getMeters().get(name(InstrumentedWithExceptionMetered.class, 335 | "meteredAndException", DEFAULT_NAME_SUFFIX)); 336 | 337 | assertThat("Guice creates a metric", 338 | meteredMetric, 339 | is(notNullValue())); 340 | 341 | assertThat("Guice creates a meter", 342 | meteredMetric, 343 | is(instanceOf(Meter.class))); 344 | 345 | assertThat("Guice creates a metric", 346 | errorMetric, 347 | is(notNullValue())); 348 | 349 | assertThat("Guice creates an exception meter", 350 | errorMetric, 351 | is(instanceOf(Meter.class))); 352 | 353 | // Counts should start at zero 354 | assertThat("Meter Metric should be zero when initialised", 355 | meteredMetric.getCount(), 356 | is(0L)); 357 | 358 | assertThat("Error Metric should be zero when initialised", 359 | errorMetric.getCount(), 360 | is(0L)); 361 | 362 | // Invoke, but don't throw an exception 363 | instance.meteredAndException(null); 364 | 365 | assertThat("Expected the meter metric to be marked on invocation", 366 | meteredMetric.getCount(), 367 | is(1L)); 368 | 369 | assertThat("Expected the exception metric to be zero since no exceptions thrown", 370 | errorMetric.getCount(), 371 | is(0L)); 372 | 373 | // Invoke and throw an exception 374 | try { 375 | instance.meteredAndException(new RuntimeException()); 376 | fail("Should have thrown an exception"); 377 | } catch (Exception ignored) { 378 | } 379 | 380 | assertThat("Expected a count of 2, one for each invocation", 381 | meteredMetric.getCount(), 382 | is(2L)); 383 | 384 | assertThat("Expected exception count to be 1 as one (of two) invocations threw an exception", 385 | errorMetric.getCount(), 386 | is(1L)); 387 | } 388 | 389 | private void assertMetricIsSetup(final Meter metric) { 390 | assertThat("Guice creates a metric", 391 | metric, 392 | is(notNullValue())); 393 | } 394 | 395 | @SuppressWarnings("serial") 396 | private static class MyOtherException extends RuntimeException { 397 | } 398 | 399 | @SuppressWarnings("serial") 400 | private static class MySpecialisedException extends MyException { 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/GaugeInheritanceTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.MetricRegistry; 4 | import com.google.inject.Guice; 5 | import com.google.inject.Injector; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static io.dropwizard.metrics5.MetricRegistry.name; 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | class GaugeInheritanceTest { 12 | 13 | @Test 14 | void testInheritance() { 15 | MetricRegistry registry = new MetricRegistry(); 16 | final Injector injector = Guice 17 | .createInjector(MetricsInstrumentationModule.builder().withMetricRegistry(registry).build()); 18 | injector.getInstance(Parent.class); 19 | injector.getInstance(Child1.class); 20 | injector.getInstance(Child2.class); 21 | 22 | // gauge in parent class is registered separately for each 23 | 24 | assertEquals(0, 25 | registry.getGauges().get(name(Parent.class, "aGauge", DeclaringClassMetricNamer.GAUGE_SUFFIX)) 26 | .getValue()); 27 | assertEquals(1, 28 | registry.getGauges().get(name(Child1.class, "aGauge", DeclaringClassMetricNamer.GAUGE_SUFFIX)) 29 | .getValue()); 30 | assertEquals(2, 31 | registry.getGauges().get(name(Child2.class, "aGauge", DeclaringClassMetricNamer.GAUGE_SUFFIX)) 32 | .getValue()); 33 | } 34 | 35 | static class Parent { 36 | 37 | @io.dropwizard.metrics5.annotation.Gauge 38 | int aGauge() { 39 | return complexInternalCalculation(); 40 | } 41 | 42 | int complexInternalCalculation() { 43 | return 0; 44 | } 45 | } 46 | 47 | static class Child1 extends Parent { 48 | @Override 49 | int complexInternalCalculation() { 50 | return 1; 51 | } 52 | } 53 | 54 | static class Child2 extends Parent { 55 | @Override 56 | int complexInternalCalculation() { 57 | return 2; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/GaugeInstanceClassNamerTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Gauge; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.dropwizard.metrics5.MetricRegistry.name; 7 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.GAUGE_SUFFIX; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | 11 | class GaugeInstanceClassNamerTest extends GaugeTestBase { 12 | @Override 13 | MetricNamer getMetricNamer() { 14 | return new GaugeInstanceClassMetricNamer(); 15 | } 16 | 17 | @Test 18 | void aGaugeWithoutNameInSuperclass() { 19 | // named for the instantiated class 20 | final Gauge metric = 21 | registry.getGauges().get(name(InstrumentedWithGauge.class, "justAGaugeFromParent", 22 | GAUGE_SUFFIX)); 23 | 24 | assertNotNull(metric); 25 | assertEquals("justAGaugeFromParent", metric.getValue()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/GaugeTestBase.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Gauge; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import com.google.inject.Guice; 6 | import com.google.inject.Injector; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static io.dropwizard.metrics5.MetricRegistry.name; 11 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.GAUGE_SUFFIX; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.Matchers.is; 14 | import static org.hamcrest.Matchers.notNullValue; 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.junit.jupiter.api.Assertions.assertNotNull; 17 | 18 | @SuppressWarnings("unchecked") 19 | abstract class GaugeTestBase { 20 | private InstrumentedWithGauge instance; 21 | MetricRegistry registry; 22 | 23 | @BeforeEach 24 | void setup() { 25 | this.registry = new MetricRegistry(); 26 | final Injector injector = 27 | Guice.createInjector(MetricsInstrumentationModule.builder() 28 | .withMetricRegistry(registry) 29 | .withMetricNamer(getMetricNamer()) 30 | .build()); 31 | this.instance = injector.getInstance(InstrumentedWithGauge.class); 32 | } 33 | 34 | abstract MetricNamer getMetricNamer(); 35 | 36 | @Test 37 | void aGaugeAnnotatedMethod() { 38 | instance.doAThing(); 39 | 40 | final Gauge metric = registry.getGauges().get(name(InstrumentedWithGauge.class, "things")); 41 | 42 | assertThat("Guice creates a metric", 43 | metric, 44 | is(notNullValue())); 45 | 46 | assertThat("Guice creates a gauge with the given value", 47 | metric.getValue(), 48 | is("poop")); 49 | } 50 | 51 | @Test 52 | void aGaugeAnnotatedMethodWithDefaultName() { 53 | instance.doAnotherThing(); 54 | 55 | final Gauge metric = registry.getGauges().get(name(InstrumentedWithGauge.class, 56 | "doAnotherThing", GAUGE_SUFFIX)); 57 | 58 | assertThat("Guice creates a metric", 59 | metric, 60 | is(notNullValue())); 61 | 62 | assertThat("Guice creates a gauge with the given value", 63 | metric.getValue(), 64 | is("anotherThing")); 65 | } 66 | 67 | @Test 68 | void aGaugeAnnotatedMethodWithAbsoluteName() { 69 | instance.doAThingWithAbsoluteName(); 70 | 71 | final Gauge metric = registry.getGauges().get(name("absoluteName")); 72 | 73 | assertThat("Guice creates a metric", 74 | metric, 75 | is(notNullValue())); 76 | 77 | assertThat("Guice creates a gauge with the given value", 78 | metric.getValue(), 79 | is("anotherThingWithAbsoluteName")); 80 | } 81 | 82 | @Test 83 | void aGaugeInSuperclass() { 84 | final Gauge metric = registry.getGauges().get(name("gaugeParent")); 85 | 86 | assertNotNull(metric); 87 | assertEquals("gaugeParent", metric.getValue()); 88 | } 89 | 90 | @Test 91 | void aPrivateGaugeInSuperclass() { 92 | final Gauge metric = registry.getGauges().get(name("gaugeParentPrivate")); 93 | 94 | assertNotNull(metric); 95 | assertEquals("gaugeParentPrivate", metric.getValue()); 96 | } 97 | 98 | @Test 99 | void aPrivateGauge() { 100 | final Gauge metric = registry.getGauges().get(name(InstrumentedWithGauge.class, "gaugePrivate")); 101 | 102 | assertNotNull(metric); 103 | assertEquals("gaugePrivate", metric.getValue()); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/GenericThing.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | @SuppressWarnings("EmptyMethod") 4 | class GenericThing { 5 | 6 | void doThing(T t) { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/InstrumentedWithCounter.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.annotation.Counted; 4 | 5 | @SuppressWarnings("UnusedReturnValue") 6 | class InstrumentedWithCounter extends InstrumentedWithCounterParent { 7 | @Counted(name = "things", monotonic = true) 8 | String doAThing() { 9 | return "poop"; 10 | } 11 | 12 | @Counted(monotonic = true) 13 | String doAnotherThing() { 14 | return "anotherThing"; 15 | } 16 | 17 | @SuppressWarnings("DefaultAnnotationParam") 18 | @Counted(monotonic = false) 19 | String doYetAnotherThing() { 20 | return "anotherThing"; 21 | } 22 | 23 | @Counted(name = "absoluteName", absolute = true, monotonic = true) 24 | String doAThingWithAbsoluteName() { 25 | return "anotherThingWithAbsoluteName"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/InstrumentedWithCounterParent.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.annotation.Counted; 4 | 5 | @SuppressWarnings("UnusedReturnValue") 6 | class InstrumentedWithCounterParent { 7 | 8 | @Counted(name = "counterParent", absolute = true) 9 | String counterParent() { 10 | return "counterParent"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/InstrumentedWithExceptionMetered.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.annotation.ExceptionMetered; 4 | import io.dropwizard.metrics5.annotation.Metered; 5 | import io.dropwizard.metrics5.annotation.Timed; 6 | 7 | @SuppressWarnings("UnusedReturnValue") 8 | class InstrumentedWithExceptionMetered { 9 | 10 | @ExceptionMetered(name = "exceptionCounter") 11 | String explodeWithPublicScope(boolean explode) { 12 | if (explode) { 13 | throw new RuntimeException("Boom!"); 14 | } else { 15 | return "calm"; 16 | } 17 | } 18 | 19 | @ExceptionMetered 20 | String explodeForUnnamedMetric() { 21 | throw new RuntimeException("Boom!"); 22 | } 23 | 24 | @ExceptionMetered(name = "n") 25 | String explodeForMetricWithName() { 26 | throw new RuntimeException("Boom!"); 27 | } 28 | 29 | @ExceptionMetered(name = "absoluteName", absolute = true) 30 | String explodeForMetricWithAbsoluteName() { 31 | throw new RuntimeException("Boom!"); 32 | } 33 | 34 | @ExceptionMetered 35 | String explodeWithDefaultScope() { 36 | throw new RuntimeException("Boom!"); 37 | } 38 | 39 | @ExceptionMetered 40 | String explodeWithProtectedScope() { 41 | throw new RuntimeException("Boom!"); 42 | } 43 | 44 | @ExceptionMetered(name = "failures", cause = MyException.class) 45 | void errorProneMethod(RuntimeException e) { 46 | throw e; 47 | } 48 | 49 | @ExceptionMetered(name = "things", 50 | cause = ArrayIndexOutOfBoundsException.class) 51 | Object causeAnOutOfBoundsException() { 52 | @SuppressWarnings("MismatchedReadAndWriteOfArray") 53 | final Object[] arr = {}; 54 | //noinspection ConstantConditions 55 | return arr[1]; 56 | } 57 | 58 | @Timed 59 | @ExceptionMetered 60 | void timedAndException(RuntimeException e) { 61 | if (e != null) { 62 | throw e; 63 | } 64 | } 65 | 66 | @Metered 67 | @ExceptionMetered 68 | void meteredAndException(RuntimeException e) { 69 | if (e != null) { 70 | throw e; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/InstrumentedWithGauge.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.annotation.Gauge; 4 | 5 | @SuppressWarnings("UnusedReturnValue") 6 | class InstrumentedWithGauge extends InstrumentedWithGaugeParent { 7 | @Gauge(name = "things") 8 | String doAThing() { 9 | return "poop"; 10 | } 11 | 12 | @Gauge 13 | String doAnotherThing() { 14 | return "anotherThing"; 15 | } 16 | 17 | @Gauge(name = "absoluteName", absolute = true) 18 | String doAThingWithAbsoluteName() { 19 | return "anotherThingWithAbsoluteName"; 20 | } 21 | 22 | @Gauge(name = "gaugePrivate") 23 | private String gaugePrivate() { 24 | return "gaugePrivate"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/InstrumentedWithGaugeParent.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.annotation.Gauge; 4 | 5 | class InstrumentedWithGaugeParent { 6 | @Gauge(name = "gaugeParent", absolute = true) 7 | public String gaugeParent() { 8 | return "gaugeParent"; 9 | } 10 | 11 | @Gauge(name = "gaugeParentPrivate", absolute = true) 12 | private String gaugeParentPrivate() { 13 | return "gaugeParentPrivate"; 14 | } 15 | 16 | @Gauge 17 | public String justAGaugeFromParent() { 18 | return "justAGaugeFromParent"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/InstrumentedWithMetered.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.annotation.Metered; 4 | 5 | @SuppressWarnings({"UnusedReturnValue", "WeakerAccess"}) 6 | class InstrumentedWithMetered { 7 | @Metered(name = "things") 8 | public String doAThing() { 9 | return "poop"; 10 | } 11 | 12 | @Metered 13 | String doAThingWithDefaultScope() { 14 | return "defaultResult"; 15 | } 16 | 17 | @Metered 18 | protected String doAThingWithProtectedScope() { 19 | return "defaultProtected"; 20 | } 21 | 22 | @Metered(name = "n") 23 | protected String doAThingWithName() { 24 | return "withName"; 25 | } 26 | 27 | @Metered(name = "nameAbs", absolute = true) 28 | protected String doAThingWithAbsoluteName() { 29 | return "absoluteName"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/InstrumentedWithTimed.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.annotation.Timed; 4 | 5 | @SuppressWarnings({"UnusedReturnValue", "WeakerAccess"}) 6 | class InstrumentedWithTimed { 7 | @Timed(name = "things") 8 | public String doAThing() throws InterruptedException { 9 | Thread.sleep(10); 10 | return "poop"; 11 | } 12 | 13 | @Timed 14 | String doAThingWithDefaultScope() { 15 | return "defaultResult"; 16 | } 17 | 18 | @Timed 19 | protected String doAThingWithProtectedScope() { 20 | return "defaultProtected"; 21 | } 22 | 23 | @Timed(name = "absoluteName", absolute = true) 24 | protected String doAThingWithAbsoluteName() { 25 | return "defaultProtected"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/MatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Meter; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import io.dropwizard.metrics5.Timer; 6 | import com.google.inject.Guice; 7 | import com.google.inject.Injector; 8 | import com.google.inject.TypeLiteral; 9 | import com.google.inject.matcher.AbstractMatcher; 10 | import com.google.inject.matcher.Matcher; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import static io.dropwizard.metrics5.MetricRegistry.name; 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.hamcrest.Matchers.is; 17 | import static org.hamcrest.Matchers.notNullValue; 18 | import static org.hamcrest.Matchers.nullValue; 19 | 20 | class MatcherTest { 21 | private InstrumentedWithTimed timedInstance; 22 | private InstrumentedWithMetered meteredInstance; 23 | private MetricRegistry registry; 24 | 25 | @BeforeEach 26 | void setup() { 27 | this.registry = new MetricRegistry(); 28 | final Matcher> matcher = new AbstractMatcher>() { 29 | @Override 30 | public boolean matches(final TypeLiteral typeLiteral) { 31 | return InstrumentedWithMetered.class.isAssignableFrom(typeLiteral.getRawType()); 32 | } 33 | }; 34 | final Injector injector = Guice.createInjector(MetricsInstrumentationModule.builder().withMetricRegistry(registry).withMatcher(matcher).build()); 35 | this.timedInstance = injector.getInstance(InstrumentedWithTimed.class); 36 | this.meteredInstance = injector.getInstance(InstrumentedWithMetered.class); 37 | } 38 | 39 | @Test 40 | void aTimedAnnotatedMethod() throws Exception { 41 | 42 | timedInstance.doAThing(); 43 | 44 | final Timer metric = registry.getTimers().get(name(InstrumentedWithTimed.class, 45 | "things")); 46 | 47 | assertThat("Guice did not create a metric for timed", 48 | metric, 49 | is(nullValue())); 50 | } 51 | 52 | @Test 53 | void aMeteredAnnotatedMethod() { 54 | 55 | meteredInstance.doAThing(); 56 | 57 | final Meter metric = registry.getMeters().get(name(InstrumentedWithMetered.class, "things")); 58 | 59 | assertThat("Guice creates a metric", 60 | metric, 61 | is(notNullValue())); 62 | 63 | assertThat("Guice creates a meter which gets marked", 64 | metric.getCount(), 65 | is(1L)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/MeteredTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.Meter; 4 | import io.dropwizard.metrics5.MetricRegistry; 5 | import com.google.inject.Guice; 6 | import com.google.inject.Injector; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static io.dropwizard.metrics5.MetricRegistry.name; 11 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.METERED_SUFFIX; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.Matchers.is; 14 | import static org.hamcrest.Matchers.notNullValue; 15 | 16 | class MeteredTest { 17 | private InstrumentedWithMetered instance; 18 | private MetricRegistry registry; 19 | 20 | @BeforeEach 21 | void setup() { 22 | this.registry = new MetricRegistry(); 23 | final Injector injector = 24 | Guice.createInjector(MetricsInstrumentationModule.builder().withMetricRegistry(registry).build()); 25 | this.instance = injector.getInstance(InstrumentedWithMetered.class); 26 | } 27 | 28 | @Test 29 | void aMeteredAnnotatedMethod() { 30 | 31 | instance.doAThing(); 32 | 33 | final Meter metric = registry.getMeters().get(name(InstrumentedWithMetered.class, "things")); 34 | 35 | assertMetricIsSetup(metric); 36 | 37 | assertThat("Guice creates a meter which gets marked", 38 | metric.getCount(), 39 | is(1L)); 40 | } 41 | 42 | @Test 43 | void aMeteredAnnotatedMethodWithDefaultScope() { 44 | 45 | final Meter metric = registry.getMeters().get(name(InstrumentedWithMetered.class, "doAThingWithDefaultScope", 46 | METERED_SUFFIX)); 47 | assertMetricIsSetup(metric); 48 | 49 | assertThat("Metric initialises to zero", 50 | metric.getCount(), 51 | is(0L)); 52 | 53 | instance.doAThingWithDefaultScope(); 54 | 55 | assertThat("Metric is marked", 56 | metric.getCount(), 57 | is(1L)); 58 | } 59 | 60 | @Test 61 | void aMeteredAnnotatedMethodWithProtectedScope() { 62 | 63 | final Meter metric = registry.getMeters().get(name(InstrumentedWithMetered.class, "doAThingWithProtectedScope", 64 | METERED_SUFFIX)); 65 | 66 | assertMetricIsSetup(metric); 67 | 68 | assertThat("Metric initialises to zero", 69 | metric.getCount(), 70 | is(0L)); 71 | 72 | instance.doAThingWithProtectedScope(); 73 | 74 | assertThat("Metric is marked", 75 | metric.getCount(), 76 | is(1L)); 77 | } 78 | 79 | @Test 80 | void aMeteredAnnotatedMethodWithName() { 81 | 82 | final Meter metric = registry.getMeters().get(name(InstrumentedWithMetered.class, "n")); 83 | 84 | assertMetricIsSetup(metric); 85 | 86 | assertThat("Metric initialises to zero", 87 | metric.getCount(), 88 | is(0L)); 89 | 90 | instance.doAThingWithName(); 91 | 92 | assertThat("Metric is marked", 93 | metric.getCount(), 94 | is(1L)); 95 | } 96 | 97 | @Test 98 | void aMeteredAnnotatedMethodWithAbsoluteName() { 99 | final Meter metric = registry.getMeters().get(name("nameAbs")); 100 | 101 | assertMetricIsSetup(metric); 102 | 103 | assertThat("Metric initialises to zero", 104 | metric.getCount(), 105 | is(0L)); 106 | 107 | instance.doAThingWithAbsoluteName(); 108 | 109 | assertThat("Metric is marked", 110 | metric.getCount(), 111 | is(1L)); 112 | } 113 | 114 | private void assertMetricIsSetup(final Meter metric) { 115 | assertThat("Guice creates a metric", 116 | metric, 117 | is(notNullValue())); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/MyException.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | class MyException extends RuntimeException { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/StringThing.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.annotation.Counted; 4 | 5 | class StringThing extends GenericThing { 6 | 7 | @Override 8 | @Counted(name = "stringThing", absolute = true, monotonic = true) 9 | void doThing(String s) { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/TimedTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice; 2 | 3 | import io.dropwizard.metrics5.MetricRegistry; 4 | import io.dropwizard.metrics5.Timer; 5 | import com.google.inject.Guice; 6 | import com.google.inject.Injector; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static io.dropwizard.metrics5.MetricRegistry.name; 11 | import static com.palominolabs.metrics.guice.DeclaringClassMetricNamer.TIMED_SUFFIX; 12 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 13 | import static java.util.concurrent.TimeUnit.NANOSECONDS; 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.Matchers.greaterThan; 16 | import static org.hamcrest.Matchers.is; 17 | import static org.hamcrest.Matchers.lessThan; 18 | import static org.hamcrest.Matchers.notNullValue; 19 | 20 | class TimedTest { 21 | private InstrumentedWithTimed instance; 22 | private MetricRegistry registry; 23 | 24 | @BeforeEach 25 | void setup() { 26 | this.registry = new MetricRegistry(); 27 | final Injector injector = Guice.createInjector(MetricsInstrumentationModule.builder().withMetricRegistry(registry).build()); 28 | this.instance = injector.getInstance(InstrumentedWithTimed.class); 29 | } 30 | 31 | @Test 32 | void aTimedAnnotatedMethod() throws Exception { 33 | 34 | instance.doAThing(); 35 | 36 | final Timer metric = registry.getTimers().get(name(InstrumentedWithTimed.class, 37 | "things")); 38 | 39 | assertMetricSetup(metric); 40 | 41 | assertThat("Guice creates a timer which records invocation length", 42 | metric.getCount(), 43 | is(1L)); 44 | 45 | assertThat("Guice creates a timer which records invocation duration without underestimating too much", 46 | metric.getSnapshot().getMax(), 47 | is(greaterThan(NANOSECONDS.convert(5, MILLISECONDS)))); 48 | 49 | assertThat("Guice creates a timer which records invocation duration without overestimating too much", 50 | metric.getSnapshot().getMax(), 51 | is(lessThan(NANOSECONDS.convert(15, MILLISECONDS)))); 52 | } 53 | 54 | @Test 55 | void aTimedAnnotatedMethodWithDefaultScope() { 56 | 57 | instance.doAThingWithDefaultScope(); 58 | 59 | final Timer metric = registry.getTimers().get(name(InstrumentedWithTimed.class, 60 | "doAThingWithDefaultScope", TIMED_SUFFIX)); 61 | 62 | assertMetricSetup(metric); 63 | } 64 | 65 | @Test 66 | void aTimedAnnotatedMethodWithProtectedScope() { 67 | 68 | instance.doAThingWithProtectedScope(); 69 | 70 | final Timer metric = registry.getTimers().get(name(InstrumentedWithTimed.class, 71 | "doAThingWithProtectedScope", TIMED_SUFFIX)); 72 | 73 | assertMetricSetup(metric); 74 | } 75 | 76 | @Test 77 | void aTimedAnnotatedMethodWithAbsoluteName() { 78 | 79 | instance.doAThingWithAbsoluteName(); 80 | 81 | final Timer metric = registry.getTimers().get(name("absoluteName")); 82 | 83 | assertMetricSetup(metric); 84 | } 85 | 86 | private void assertMetricSetup(final Timer metric) { 87 | assertThat("Guice creates a metric", 88 | metric, 89 | is(notNullValue())); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/annotation/ClassAnnotationResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice.annotation; 2 | 3 | import io.dropwizard.metrics5.annotation.Counted; 4 | import io.dropwizard.metrics5.annotation.Metered; 5 | import io.dropwizard.metrics5.annotation.Timed; 6 | import java.lang.reflect.Method; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertNotNull; 11 | 12 | class ClassAnnotationResolverTest { 13 | 14 | @Test 15 | void testTypeLevelAnnotations() throws Exception { 16 | AnnotationResolver matcher = new ClassAnnotationResolver(); 17 | Class klass = TypeLevelAnnotatedClass.class; 18 | Method publicMethod = klass.getDeclaredMethod("publicMethod"); 19 | Method protectedMethod = klass.getDeclaredMethod("protectedMethod"); 20 | Method packagePrivateMethod = klass.getDeclaredMethod("packagePrivateMethod"); 21 | 22 | assertNotNull(matcher.findAnnotation(Timed.class, publicMethod)); 23 | assertNotNull(matcher.findAnnotation(Metered.class, publicMethod)); 24 | assertNotNull(matcher.findAnnotation(Counted.class, publicMethod)); 25 | 26 | assertNotNull(matcher.findAnnotation(Timed.class, protectedMethod)); 27 | assertNotNull(matcher.findAnnotation(Metered.class, protectedMethod)); 28 | assertNotNull(matcher.findAnnotation(Counted.class, protectedMethod)); 29 | 30 | assertNotNull(matcher.findAnnotation(Timed.class, packagePrivateMethod)); 31 | assertNotNull(matcher.findAnnotation(Metered.class, packagePrivateMethod)); 32 | assertNotNull(matcher.findAnnotation(Counted.class, packagePrivateMethod)); 33 | } 34 | 35 | @SuppressWarnings("WeakerAccess") 36 | @Timed 37 | @Metered 38 | @Counted 39 | private static class TypeLevelAnnotatedClass { 40 | public void publicMethod() { 41 | } 42 | 43 | protected void protectedMethod() { 44 | } 45 | 46 | void packagePrivateMethod() { 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/annotation/ListAnnotationResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice.annotation; 2 | 3 | import io.dropwizard.metrics5.annotation.Counted; 4 | import io.dropwizard.metrics5.annotation.Metered; 5 | import io.dropwizard.metrics5.annotation.Timed; 6 | import com.google.common.collect.Lists; 7 | import java.lang.reflect.Method; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertFalse; 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | import static org.junit.jupiter.api.Assertions.assertNull; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | class ListAnnotationResolverTest { 17 | 18 | @Test 19 | void testMixedAnnotations() throws Exception { 20 | ListAnnotationResolver annotationProvider = new ListAnnotationResolver( 21 | Lists.newArrayList( 22 | new MethodAnnotationResolver(), 23 | new ClassAnnotationResolver() 24 | ) 25 | ); 26 | 27 | Class klass = MixedAnnotatedClass.class; 28 | Method publicMethod = klass.getDeclaredMethod("publicMethod"); 29 | Method protectedMethod = klass.getDeclaredMethod("protectedMethod"); 30 | Method packagePrivateMethod = klass.getDeclaredMethod("packagePrivateMethod"); 31 | 32 | Timed classTimed = annotationProvider.findAnnotation(Timed.class, publicMethod); 33 | assertNotNull(classTimed); 34 | assertFalse(classTimed.absolute()); 35 | assertNull(annotationProvider.findAnnotation(Metered.class, publicMethod)); 36 | assertNull(annotationProvider.findAnnotation(Counted.class, publicMethod)); 37 | 38 | assertNotNull(annotationProvider.findAnnotation(Timed.class, protectedMethod)); 39 | assertNotNull(annotationProvider.findAnnotation(Metered.class, protectedMethod)); 40 | assertNull(annotationProvider.findAnnotation(Counted.class, protectedMethod)); 41 | 42 | Timed methodTimed = annotationProvider.findAnnotation(Timed.class, packagePrivateMethod); 43 | assertNotNull(methodTimed); 44 | assertTrue(methodTimed.absolute()); 45 | assertNull(annotationProvider.findAnnotation(Metered.class, packagePrivateMethod)); 46 | assertNull(annotationProvider.findAnnotation(Counted.class, packagePrivateMethod)); 47 | } 48 | 49 | @SuppressWarnings("WeakerAccess") 50 | @Timed 51 | private static class MixedAnnotatedClass { 52 | public void publicMethod() { 53 | } 54 | 55 | @Metered 56 | protected void protectedMethod() { 57 | } 58 | 59 | @Timed(absolute = true) 60 | void packagePrivateMethod() { 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/palominolabs/metrics/guice/annotation/MethodAnnotationResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.palominolabs.metrics.guice.annotation; 2 | 3 | import io.dropwizard.metrics5.annotation.Counted; 4 | import io.dropwizard.metrics5.annotation.Metered; 5 | import io.dropwizard.metrics5.annotation.Timed; 6 | import java.lang.reflect.Method; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertNotNull; 11 | import static org.junit.jupiter.api.Assertions.assertNull; 12 | 13 | class MethodAnnotationResolverTest { 14 | @Test 15 | void testMethodAnnotations() throws Exception { 16 | AnnotationResolver matcher = new MethodAnnotationResolver(); 17 | Class klass = MethodAnnotatedClass.class; 18 | Method publicMethod = klass.getDeclaredMethod("publicMethod"); 19 | Method protectedMethod = klass.getDeclaredMethod("protectedMethod"); 20 | Method packagePrivateMethod = klass.getDeclaredMethod("packagePrivateMethod"); 21 | 22 | assertNotNull(matcher.findAnnotation(Timed.class, publicMethod)); 23 | assertNotNull(matcher.findAnnotation(Metered.class, protectedMethod)); 24 | assertNotNull(matcher.findAnnotation(Counted.class, packagePrivateMethod)); 25 | 26 | assertNull(matcher.findAnnotation(Timed.class, packagePrivateMethod)); 27 | assertNull(matcher.findAnnotation(Counted.class, protectedMethod)); 28 | assertNull(matcher.findAnnotation(Metered.class, publicMethod)); 29 | } 30 | 31 | @SuppressWarnings("WeakerAccess") 32 | private static class MethodAnnotatedClass { 33 | @Timed 34 | public void publicMethod() { 35 | } 36 | 37 | @Metered 38 | protected void protectedMethod() { 39 | } 40 | 41 | @Counted 42 | void packagePrivateMethod() { 43 | } 44 | } 45 | } 46 | --------------------------------------------------------------------------------