├── .editorconfig ├── .github └── workflows │ ├── matrix.yml │ └── snapshot.yml ├── .gitignore ├── .mvn ├── maven.config └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── build ├── ide-config │ ├── eclipse-format.xml │ └── eclipse.importorder └── pom.xml ├── deployment ├── pom.xml └── src │ ├── main │ └── java │ │ └── dev │ │ └── ebullient │ │ └── micrometer │ │ └── deployment │ │ ├── MicrometerProcessor.java │ │ ├── MicrometerRegistryProviderBuildItem.java │ │ ├── RootMeterRegistryBuildItem.java │ │ ├── binder │ │ ├── HibernateBinderProcessor.java │ │ ├── VertxBinderProcessor.java │ │ └── mpmetrics │ │ │ ├── AnnotationHandler.java │ │ │ ├── GaugeAnnotationHandler.java │ │ │ ├── MetricAnnotationInfo.java │ │ │ ├── MetricDotNames.java │ │ │ └── MicroprofileMetricsProcessor.java │ │ └── export │ │ ├── DatadogRegistryProcessor.java │ │ ├── JmxRegistryProcessor.java │ │ ├── PrometheusRegistryProcessor.java │ │ └── StackdriverRegistryProcessor.java │ └── test │ ├── java │ └── dev │ │ └── ebullient │ │ ├── micrometer │ │ ├── deployment │ │ │ ├── GlobalDefaultDisabledTest.java │ │ │ ├── MetricsFromMetricsFactoryTestCase.java │ │ │ ├── MicrometerDisabledTest.java │ │ │ ├── binder │ │ │ │ └── MpMetricNamingTest.java │ │ │ └── export │ │ │ │ ├── AllRegistriesDisabledTest.java │ │ │ │ ├── DatadogEnabledInvalidTest.java │ │ │ │ ├── DatadogEnabledTest.java │ │ │ │ ├── JmxEnabledTest.java │ │ │ │ ├── PrometheusEnabledTest.java │ │ │ │ ├── SecondPrometheusProvider.java │ │ │ │ ├── SecondPrometheusTest.java │ │ │ │ ├── StackdriverEnabledInvalidTest.java │ │ │ │ └── StackdriverEnabledTest.java │ │ └── runtime │ │ │ └── binder │ │ │ └── mpmetrics │ │ │ └── MpMetricRegistrationTest.java │ │ └── test │ │ ├── MeasureThis.java │ │ └── MpColorResource.java │ └── resources │ └── test-logging.properties ├── integration-tests ├── jmx │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── dev │ │ │ │ └── ebullient │ │ │ │ └── it │ │ │ │ └── micrometer │ │ │ │ └── jmx │ │ │ │ ├── CustomConfiguration.java │ │ │ │ └── MessageResource.java │ │ └── resources │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── dev │ │ └── ebullient │ │ └── it │ │ └── micrometer │ │ └── jmx │ │ └── JmxMetricsRegistryTest.java ├── mp-metrics │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── dev │ │ │ │ └── ebullient │ │ │ │ └── it │ │ │ │ └── micrometer │ │ │ │ └── mpmetrics │ │ │ │ ├── CountedInstance.java │ │ │ │ ├── InjectedInstance.java │ │ │ │ ├── PrimeResource.java │ │ │ │ └── RenameMeterFilterProducer.java │ │ └── resources │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── dev │ │ └── ebullient │ │ └── it │ │ └── micrometer │ │ └── mpmetrics │ │ ├── MPMetricsIT.java │ │ └── MPMetricsTest.java ├── pom.xml └── prometheus │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── ebullient │ │ │ └── it │ │ │ └── micrometer │ │ │ └── prometheus │ │ │ ├── CustomConfiguration.java │ │ │ ├── Fruit.java │ │ │ ├── FruitResource.java │ │ │ └── MessageResource.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── dev │ └── ebullient │ └── it │ └── micrometer │ └── prometheus │ ├── PrometheusMetricsRegistryIT.java │ ├── PrometheusMetricsRegistryTest.java │ └── TestResources.java ├── jitpack.yml ├── mvnw ├── mvnw.cmd ├── pom.xml └── runtime ├── pom.xml └── src ├── main ├── java │ └── dev │ │ └── ebullient │ │ └── micrometer │ │ └── runtime │ │ ├── ClockProvider.java │ │ ├── CompositeRegistryCreator.java │ │ ├── MeterFilterConstraint.java │ │ ├── MeterFilterConstraints.java │ │ ├── MicrometerMetricsFactory.java │ │ ├── MicrometerRecorder.java │ │ ├── binder │ │ ├── HibernateMetricsRecorder.java │ │ ├── JvmInfoMetrics.java │ │ ├── mpmetrics │ │ │ ├── AnnotatedGaugeAdapter.java │ │ │ ├── ConcurrentGaugeImpl.java │ │ │ ├── ConcurrentGaugeInterceptor.java │ │ │ ├── CountedInterceptor.java │ │ │ ├── CounterAdapter.java │ │ │ ├── GaugeAdapter.java │ │ │ ├── HistogramAdapter.java │ │ │ ├── InjectedMetricProducer.java │ │ │ ├── MeterAdapter.java │ │ │ ├── MeterHolder.java │ │ │ ├── MetricDescriptor.java │ │ │ ├── MetricRegistryAdapter.java │ │ │ ├── MpMetadata.java │ │ │ ├── MpMetricsBinder.java │ │ │ ├── MpMetricsRecorder.java │ │ │ ├── MpMetricsRegistryProducer.java │ │ │ ├── TimedInterceptor.java │ │ │ └── TimerAdapter.java │ │ └── vertx │ │ │ ├── MetricsContext.java │ │ │ ├── VertxHttpServerMetrics.java │ │ │ ├── VertxMeterBinderAdapter.java │ │ │ ├── VertxMeterBinderContainerFilter.java │ │ │ ├── VertxMeterBinderRecorder.java │ │ │ ├── VertxMeterFilter.java │ │ │ ├── VertxMetricsTags.java │ │ │ ├── VertxNetworkMetrics.java │ │ │ └── VertxTcpMetrics.java │ │ ├── config │ │ ├── DatadogConfig.java │ │ ├── JmxConfig.java │ │ ├── MicrometerConfig.java │ │ ├── MicroprofileMetricsConfig.java │ │ ├── PrometheusConfig.java │ │ ├── StackdriverConfig.java │ │ ├── VertxConfig.java │ │ └── runtime │ │ │ ├── ExportConfig.java │ │ │ └── VertxConfig.java │ │ └── export │ │ ├── DatadogMeterRegistryProvider.java │ │ ├── JmxMeterRegistryProvider.java │ │ ├── PrometheusMeterRegistryProvider.java │ │ ├── PrometheusRecorder.java │ │ ├── StackdriverMeterRegistryProvider.java │ │ └── handlers │ │ └── PrometheusHandler.java └── resources │ └── META-INF │ └── quarkus-extension.yaml └── test └── java ├── dev.ebullient.micrometer.runtime.binder.vertx └── VertxMetricsTagsTest.java └── dev └── ebullient └── micrometer └── runtime └── binder └── JvmMetricsInfoTest.java /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | 14 | [*.{md}] 15 | indent_size = 2 16 | trim_trailing_whitespace = false 17 | 18 | [*.java] 19 | indent_size = 4 20 | 21 | [*.{yaml,yml}] 22 | indent_size = 2 23 | 24 | [*.{html,js,rb,css,xml,sh}] 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /.github/workflows/matrix.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master, 1.6.x ] 9 | pull_request: 10 | branches: [ master, 1.6.x ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-18.04 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | java: [ 8, 11 ] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions/cache@v1 23 | with: 24 | path: ~/.m2/repository 25 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 26 | restore-keys: | 27 | ${{ runner.os }}-maven- 28 | 29 | - name: Install JDK ${{ matrix.java }} 30 | uses: actions/setup-java@v1 31 | with: 32 | java-version: ${{ matrix.java }} 33 | 34 | - name: Build with Maven 35 | run: | 36 | ./mvnw -B --no-transfer-progress help:evaluate -Dexpression=project.version | grep -v INFO 37 | ./mvnw -B --no-transfer-progress clean verify -Dformat.skip 38 | 39 | native: 40 | needs: build 41 | runs-on: ubuntu-18.04 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | java: [ 8, 11 ] 46 | graal: [ 20.1.0 ] 47 | 48 | steps: 49 | - uses: actions/checkout@v2 50 | - uses: actions/cache@v1 51 | with: 52 | path: ~/.m2/repository 53 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 54 | restore-keys: | 55 | ${{ runner.os }}-maven- 56 | 57 | - name: Install JDK ${{ matrix.java }} 58 | uses: actions/setup-java@v1 59 | with: 60 | java-version: ${{ matrix.java }} 61 | 62 | - name: Pull Quarkus Native Builder Image ${{ matrix.graal }}-java${{ matrix.java }} 63 | run: docker pull quay.io/quarkus/ubi-quarkus-native-image:${{ matrix.graal }}-java${{ matrix.java }} 64 | 65 | - name: Native Quarkus build with maven 66 | run: | 67 | ./mvnw -B --no-transfer-progress clean verify -Dformat.skip -Dnative -Dquarkus.native.native-image-xmx=3g -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:${{ matrix.graal }}-java${{ matrix.java }} 68 | -------------------------------------------------------------------------------- /.github/workflows/snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Update snapshot tag 2 | on: 3 | push: 4 | branches: [ master ] 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-18.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/cache@v1 12 | with: 13 | path: ~/.m2/repository 14 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 15 | restore-keys: | 16 | ${{ runner.os }}-maven- 17 | - uses: actions/setup-java@v1 18 | with: 19 | java-version: 11 20 | 21 | - name: Build with Maven 22 | run: | 23 | ./mvnw -B --no-transfer-progress help:evaluate -Dexpression=project.version | grep -v INFO 24 | ./mvnw -B --no-transfer-progress clean verify 25 | 26 | - name: Update tag 27 | if: success() 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | JITPACK_TOKEN: ${{ secrets.JITPACK_TOKEN }} 31 | shell: bash 32 | run: | 33 | VERSION=$(./mvnw -B --no-transfer-progress help:evaluate -Dexpression=project.version | grep -v INFO) 34 | echo $VERSION 35 | if [[ $VERSION == [.0-9]*-SNAPSHOT ]]; then 36 | git config user.name "${GITHUB_ACTOR}" 37 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 38 | 39 | echo "Update tag for $VERSION" 40 | git push origin :refs/tags/$VERSION 41 | git tag -f $VERSION 42 | git push --tags 43 | 44 | echo "Delete JitPack artifacts for tag" 45 | curl -X "DELETE" -u${JITPACK_TOKEN} https://jitpack.io/api/builds/dev.ebullient/quarkus-micrometer-extension/$VERSION 46 | fi 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | nohup.out 3 | tmp 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### VS Code ### 21 | .vscode/ 22 | 23 | # Eclipse 24 | .project 25 | .classpath 26 | .settings/ 27 | bin/ 28 | 29 | # NetBeans 30 | nb-configuration.xml 31 | 32 | # OSX 33 | .DS_Store 34 | 35 | # Vim 36 | *.swp 37 | *.swo 38 | 39 | # patch 40 | *.orig 41 | *.rej 42 | 43 | # Maven 44 | target/ 45 | .flattened* 46 | pom.xml.tag 47 | pom.xml.releaseBackup 48 | pom.xml.versionsBackup 49 | release.properties 50 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Drevision=1.7.0-SNAPSHOT 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebullient/quarkus-micrometer-extension/a539b69a98de31f4e04e4fe59ecda24241270ae8/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quarkus-micrometer-extension 2 | 3 | This project has been archived. 4 | 5 | Support for Micrometer and Prometheus has been added to Quarkus. See [Quarkus Micrometer Guide](https://quarkus.io/guides/micrometer) for information on using the Quarkus Micrometer extension. 6 | 7 | Support for additional Monitoring backends come from extensions in the [quarkiverse/quarkus-micrometer-registry](https://github.com/quarkiverse/quarkus-micrometer-registry) repository. Contributions are welcome! 8 | 9 | -------------------------------------------------------------------------------- /build/ide-config/eclipse.importorder: -------------------------------------------------------------------------------- 1 | #Organize Import Order 2 | #Wed Jan 23 12:03:29 AEDT 2019 3 | 0=java 4 | 1=javax 5 | 2=org 6 | 3=com 7 | -------------------------------------------------------------------------------- /deployment/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 4.0.0 20 | 21 | 22 | dev.ebullient.quarkus-micrometer-extension 23 | quarkus-micrometer-build 24 | ${revision} 25 | ../build/pom.xml 26 | 27 | 28 | quarkus-micrometer-deployment 29 | Micrometer Quarkus extension - Deployment 30 | 31 | 32 | 33 | ../ 34 | 35 | 36 | 37 | 38 | ${project.groupId} 39 | quarkus-micrometer 40 | ${project.version} 41 | 42 | 43 | 44 | io.quarkus 45 | quarkus-core-deployment 46 | 47 | 48 | io.quarkus 49 | quarkus-arc-deployment 50 | 51 | 52 | io.quarkus 53 | quarkus-vertx-http-deployment 54 | 55 | 56 | io.quarkus 57 | quarkus-resteasy-server-common-deployment 58 | 59 | 60 | 61 | io.micrometer 62 | micrometer-core 63 | 64 | 65 | 66 | 67 | 68 | io.quarkus 69 | quarkus-hibernate-orm-deployment 70 | true 71 | 72 | 73 | 74 | 75 | 76 | io.micrometer 77 | micrometer-registry-datadog 78 | true 79 | 80 | 81 | io.micrometer 82 | micrometer-registry-jmx 83 | true 84 | 85 | 86 | io.micrometer 87 | micrometer-registry-prometheus 88 | true 89 | 90 | 91 | io.micrometer 92 | micrometer-registry-stackdriver 93 | true 94 | 95 | 96 | 97 | 98 | 99 | io.quarkus 100 | quarkus-junit5-internal 101 | test 102 | 103 | 104 | 105 | io.quarkus 106 | quarkus-resteasy-deployment 107 | test 108 | 109 | 110 | 111 | io.quarkus 112 | quarkus-resteasy-jackson-deployment 113 | test 114 | 115 | 116 | 117 | io.rest-assured 118 | rest-assured 119 | test 120 | 121 | 122 | 123 | org.eclipse.microprofile.metrics 124 | microprofile-metrics-api 125 | test 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | maven-surefire-plugin 134 | 135 | 136 | org.jboss.logmanager.LogManager 137 | INFO 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/MicrometerRegistryProviderBuildItem.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment; 2 | 3 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import io.quarkus.builder.item.MultiBuildItem; 6 | 7 | @SuppressWarnings("unchecked") 8 | public final class MicrometerRegistryProviderBuildItem extends MultiBuildItem { 9 | 10 | final Class clazz; 11 | 12 | public MicrometerRegistryProviderBuildItem(Class providedRegistryClass) { 13 | this.clazz = (Class) providedRegistryClass; 14 | } 15 | 16 | public MicrometerRegistryProviderBuildItem(String registryClassName) { 17 | this.clazz = (Class) MicrometerRecorder.getClassForName(registryClassName); 18 | } 19 | 20 | public Class getRegistryClass() { 21 | return clazz; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "MicrometerRegistryProviderBuildItem{" 27 | + clazz 28 | + '}'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/RootMeterRegistryBuildItem.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment; 2 | 3 | import io.micrometer.core.instrument.MeterRegistry; 4 | import io.quarkus.builder.item.SimpleBuildItem; 5 | import io.quarkus.runtime.RuntimeValue; 6 | 7 | public final class RootMeterRegistryBuildItem extends SimpleBuildItem { 8 | private final RuntimeValue value; 9 | 10 | public RootMeterRegistryBuildItem(RuntimeValue value) { 11 | this.value = value; 12 | } 13 | 14 | public RuntimeValue getValue() { 15 | return value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/binder/HibernateBinderProcessor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.binder; 2 | 3 | import dev.ebullient.micrometer.runtime.binder.HibernateMetricsRecorder; 4 | import io.quarkus.arc.deployment.BeanContainerBuildItem; 5 | import io.quarkus.deployment.Capabilities; 6 | import io.quarkus.deployment.annotations.BuildStep; 7 | import io.quarkus.deployment.annotations.ExecutionTime; 8 | import io.quarkus.deployment.annotations.Record; 9 | 10 | public class HibernateBinderProcessor { 11 | @BuildStep(onlyIf = VertxBinderProcessor.VertxBinderEnabled.class) 12 | @Record(value = ExecutionTime.RUNTIME_INIT) 13 | void createHibernateMicrometerBinders(Capabilities capabilities, 14 | BeanContainerBuildItem beanContainer, 15 | HibernateMetricsRecorder recorder) { 16 | 17 | if (!capabilities.isCapabilityPresent(Capabilities.HIBERNATE_ORM)) { 18 | return; 19 | } 20 | 21 | recorder.registerMetrics(beanContainer.getValue()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/binder/VertxBinderProcessor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.binder; 2 | 3 | import java.util.function.BooleanSupplier; 4 | 5 | import javax.interceptor.Interceptor; 6 | 7 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 8 | import dev.ebullient.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter; 9 | import dev.ebullient.micrometer.runtime.binder.vertx.VertxMeterBinderContainerFilter; 10 | import dev.ebullient.micrometer.runtime.binder.vertx.VertxMeterBinderRecorder; 11 | import dev.ebullient.micrometer.runtime.binder.vertx.VertxMeterFilter; 12 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig; 13 | import dev.ebullient.micrometer.runtime.config.runtime.VertxConfig; 14 | import io.quarkus.arc.deployment.AdditionalBeanBuildItem; 15 | import io.quarkus.deployment.annotations.BuildStep; 16 | import io.quarkus.deployment.annotations.ExecutionTime; 17 | import io.quarkus.deployment.annotations.Record; 18 | import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem; 19 | import io.quarkus.vertx.core.deployment.VertxOptionsConsumerBuildItem; 20 | import io.quarkus.vertx.http.deployment.FilterBuildItem; 21 | 22 | public class VertxBinderProcessor { 23 | static final String METRIC_OPTIONS_CLASS_NAME = "io.vertx.core.metrics.MetricsOptions"; 24 | static final Class METRIC_OPTIONS_CLASS = MicrometerRecorder.getClassForName(METRIC_OPTIONS_CLASS_NAME); 25 | 26 | static class VertxBinderEnabled implements BooleanSupplier { 27 | MicrometerConfig mConfig; 28 | 29 | public boolean getAsBoolean() { 30 | return METRIC_OPTIONS_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.vertx); 31 | } 32 | } 33 | 34 | @BuildStep(onlyIf = VertxBinderEnabled.class) 35 | AdditionalBeanBuildItem createVertxAdapters() { 36 | // Add Vertx meter adapters 37 | return AdditionalBeanBuildItem.builder() 38 | .addBeanClass(VertxMeterBinderAdapter.class) 39 | .addBeanClass(VertxMeterBinderContainerFilter.class) 40 | .setUnremovable().build(); 41 | } 42 | 43 | @BuildStep(onlyIf = VertxBinderEnabled.class) 44 | ResteasyJaxrsProviderBuildItem createVertxFilters() { 45 | return new ResteasyJaxrsProviderBuildItem(VertxMeterBinderContainerFilter.class.getName()); 46 | } 47 | 48 | @BuildStep(onlyIf = VertxBinderEnabled.class) 49 | FilterBuildItem addVertxMeterFilter() { 50 | return new FilterBuildItem(new VertxMeterFilter(), 10); 51 | } 52 | 53 | @BuildStep(onlyIf = VertxBinderEnabled.class) 54 | @Record(value = ExecutionTime.STATIC_INIT) 55 | VertxOptionsConsumerBuildItem build(VertxMeterBinderRecorder recorder) { 56 | return new VertxOptionsConsumerBuildItem(recorder.configureMetricsAdapter(), Interceptor.Priority.LIBRARY_AFTER); 57 | } 58 | 59 | @BuildStep(onlyIf = VertxBinderEnabled.class) 60 | @Record(value = ExecutionTime.RUNTIME_INIT) 61 | void setVertxConfig(VertxMeterBinderRecorder recorder, VertxConfig config) { 62 | recorder.setVertxConfig(config); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/binder/mpmetrics/MetricAnnotationInfo.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.binder.mpmetrics; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import org.jboss.jandex.*; 8 | import org.jboss.logging.Logger; 9 | 10 | import io.quarkus.arc.processor.DotNames; 11 | 12 | public class MetricAnnotationInfo { 13 | private static final Logger log = Logger.getLogger(MetricAnnotationInfo.class); 14 | 15 | List output = new ArrayList<>(); 16 | 17 | String name; 18 | String description; 19 | String unit; 20 | String[] tags; 21 | 22 | MetricAnnotationInfo(AnnotationInstance input, IndexView index, ClassInfo classInfo, MethodInfo method, 23 | FieldInfo fieldInfo) { 24 | output.add(input.valueWithDefault(index, "displayName")); 25 | 26 | // Remember the unit 27 | AnnotationValue value = input.value("unit"); 28 | if (value != null) { 29 | output.add(value); 30 | if (!"none".equals(value.asString())) { 31 | unit = value.asString(); 32 | } 33 | } 34 | 35 | // Remember absolute 36 | value = input.valueWithDefault(index, "absolute"); 37 | output.add(value); 38 | boolean absolute = value.asBoolean(); 39 | 40 | // Assign a name. Start with the name in the annotation... 41 | name = input.valueWithDefault(index, "name").asString(); 42 | if (input.target().kind() == AnnotationTarget.Kind.FIELD) { 43 | String fieldName = fieldInfo.name(); 44 | if (absolute) { 45 | name = name.isEmpty() ? fieldName : name; 46 | } else { 47 | name = append(classInfo.name().toString(), name.isEmpty() ? fieldName : name); 48 | } 49 | } 50 | if (input.target().kind() == AnnotationTarget.Kind.METHOD) { 51 | String methodName = method.name().replace("", classInfo.simpleName()); 52 | if (absolute) { 53 | name = name.isEmpty() ? methodName : name; 54 | } else { 55 | name = append(classInfo.name().toString(), name.isEmpty() ? methodName : name); 56 | } 57 | } 58 | if (input.target().kind() == AnnotationTarget.Kind.CLASS) { 59 | String methodName = method == null ? "" : method.name(); 60 | if (absolute) { 61 | name = append(name.isEmpty() ? classInfo.simpleName() : name, methodName); 62 | } else { 63 | DotName className = classInfo.name(); 64 | if (name.isEmpty()) { 65 | name = append(className.toString(), methodName); 66 | } else { 67 | name = append(DotNames.packageName(className), name, methodName); 68 | } 69 | } 70 | } 71 | 72 | output.add(AnnotationValue.createStringValue("name", name)); 73 | 74 | description = input.valueWithDefault(index, "description").asString(); 75 | output.add(AnnotationValue.createStringValue("description", description)); 76 | 77 | tags = createTags(input, index); 78 | AnnotationValue[] tagValues = new AnnotationValue[tags.length]; 79 | for (int i = 0; i < tags.length; i++) { 80 | tagValues[i] = AnnotationValue.createStringValue("tags", tags[i]); 81 | } 82 | output.add(AnnotationValue.createArrayValue("tags", tagValues)); 83 | 84 | log.debugf("%s --> name='%s', description='%s', unit='%s', tags='%s'", 85 | input, name, description, 86 | unit == null ? "none" : unit, 87 | Arrays.asList(tags)); 88 | } 89 | 90 | static String append(String... values) { 91 | StringBuilder b = new StringBuilder(); 92 | for (String s : values) { 93 | if (b.length() > 0 && !s.isEmpty()) { 94 | b.append('.'); 95 | } 96 | b.append(s); 97 | } 98 | return b.toString(); 99 | } 100 | 101 | static String[] createTags(AnnotationInstance annotation, IndexView index) { 102 | List tags = new ArrayList<>(); 103 | tags.add("scope"); 104 | tags.add("application"); 105 | 106 | for (String s : annotation.valueWithDefault(index, "tags").asStringArray()) { 107 | // separate key=value strings into two parts 108 | int pos = s.indexOf('='); 109 | if (pos > 0 && s.length() > 2) { 110 | tags.add(s.substring(0, pos)); 111 | tags.add(s.substring(pos + 1)); 112 | } else { 113 | tags.add(s); 114 | } 115 | } 116 | if (tags.size() % 2 == 1) { 117 | log.warnf("Problem parsing tag values from %s", annotation); 118 | } 119 | return tags.toArray(new String[tags.size()]); 120 | } 121 | 122 | public AnnotationValue[] getAnnotationValues() { 123 | return output.toArray(new AnnotationValue[0]); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/binder/mpmetrics/MetricDotNames.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.binder.mpmetrics; 2 | 3 | import java.util.*; 4 | 5 | import org.jboss.jandex.AnnotationInstance; 6 | import org.jboss.jandex.ClassInfo; 7 | import org.jboss.jandex.DotName; 8 | 9 | import io.quarkus.arc.processor.BuiltinScope; 10 | 11 | /** 12 | * The microprofile API must remain optional. 13 | * 14 | * Avoid importing classes that import MP Metrics API classes. 15 | */ 16 | public class MetricDotNames { 17 | static final String MICROMETER_EXTENSION_PKG = "dev.ebullient.micrometer.runtime.binder.mpmetrics"; 18 | 19 | // Use string class names: do not force-load a class that pulls in microprofile dependencies 20 | static final DotName MP_METRICS_BINDER = DotName 21 | .createSimple("dev.ebullient.micrometer.runtime.binder.mpmetrics.MpMetricsBinder"); 22 | static final DotName CONCURRENT_GAUGE_ANNOTATION = DotName 23 | .createSimple("org.eclipse.microprofile.metrics.annotation.ConcurrentGauge"); 24 | static final DotName COUNTED_ANNOTATION = DotName.createSimple("org.eclipse.microprofile.metrics.annotation.Counted"); 25 | static final DotName GAUGE_ANNOTATION = DotName.createSimple("org.eclipse.microprofile.metrics.annotation.Gauge"); 26 | static final DotName METERED_ANNOTATION = DotName.createSimple("org.eclipse.microprofile.metrics.annotation.Metered"); 27 | static final DotName SIMPLY_TIMED_ANNOTATION = DotName 28 | .createSimple("org.eclipse.microprofile.metrics.annotation.SimplyTimed"); 29 | static final DotName TIMED_ANNOTATION = DotName.createSimple("org.eclipse.microprofile.metrics.annotation.Timed"); 30 | 31 | static final Set individualMetrics = new HashSet<>(Arrays.asList( 32 | CONCURRENT_GAUGE_ANNOTATION, 33 | COUNTED_ANNOTATION, 34 | GAUGE_ANNOTATION, 35 | METERED_ANNOTATION, 36 | SIMPLY_TIMED_ANNOTATION, 37 | TIMED_ANNOTATION)); 38 | 39 | static final DotName METRIC_ANNOTATION = DotName 40 | .createSimple("org.eclipse.microprofile.metrics.annotation.Metric"); 41 | static final DotName ANNOTATED_GAUGE_ADAPTER = DotName 42 | .createSimple("dev.ebullient.micrometer.runtime.binder.mpmetrics.AnnotatedGaugeAdapter"); 43 | 44 | static final DotName METRIC = DotName 45 | .createSimple("org.eclipse.microprofile.metrics.Metric"); 46 | 47 | // these are needed for determining whether a class is a REST endpoint or JAX-RS provider 48 | static final DotName JAXRS_PATH = DotName.createSimple("javax.ws.rs.Path"); 49 | static final DotName REST_CONTROLLER = DotName 50 | .createSimple("org.springframework.web.bind.annotation.RestController"); 51 | 52 | // Interceptors and producers 53 | static final DotName CONCURRENT_GAUGE_INTERCEPTOR = DotName 54 | .createSimple("dev.ebullient.micrometer.runtime.binder.mpmetrics.ConcurrentGaugeInterceptor"); 55 | static final DotName COUNTED_INTERCEPTOR = DotName 56 | .createSimple("dev.ebullient.micrometer.runtime.binder.mpmetrics.CountedInterceptor"); 57 | static final DotName INJECTED_METRIC_PRODUCER = DotName 58 | .createSimple("dev.ebullient.micrometer.runtime.binder.mpmetrics.InjectedMetricProducer"); 59 | static final DotName TIMED_INTERCEPTOR = DotName 60 | .createSimple("dev.ebullient.micrometer.runtime.binder.mpmetrics.TimedInterceptor"); 61 | static final DotName MP_METRICS_REGISTRY_PRODUCER = DotName 62 | .createSimple("dev.ebullient.micrometer.runtime.binder.mpmetrics.MpMetricsRegistryProducer"); 63 | 64 | /** 65 | * @param annotations 66 | * @return true if the map of all annotations contains any MP Metrics 67 | * annotations 68 | */ 69 | static boolean containsMetricAnnotation(Map> annotations) { 70 | for (DotName name : individualMetrics) { 71 | if (annotations.containsKey(name)) { 72 | return true; 73 | } 74 | } 75 | return false; 76 | } 77 | 78 | /** 79 | * @return true for known metrics subsystem classes that should not 80 | * be inspected for lifecycle constraints, etc. 81 | */ 82 | static boolean knownClass(ClassInfo classInfo) { 83 | return classInfo.name().toString().startsWith(MICROMETER_EXTENSION_PKG); 84 | } 85 | 86 | /** 87 | * @param classInfo 88 | * @return true if the specified class is either a REST endpoint or 89 | * has a singleton/application scope. 90 | */ 91 | static boolean isSingleInstance(ClassInfo classInfo) { 92 | BuiltinScope beanScope = BuiltinScope.from(classInfo); 93 | return classInfo.annotations().containsKey(REST_CONTROLLER) || 94 | classInfo.annotations().containsKey(JAXRS_PATH) || 95 | BuiltinScope.APPLICATION.equals(beanScope) || 96 | BuiltinScope.SINGLETON.equals(beanScope); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/export/DatadogRegistryProcessor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.function.BooleanSupplier; 4 | 5 | import dev.ebullient.micrometer.deployment.MicrometerRegistryProviderBuildItem; 6 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 7 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig; 8 | import dev.ebullient.micrometer.runtime.export.DatadogMeterRegistryProvider; 9 | import io.quarkus.arc.deployment.AdditionalBeanBuildItem; 10 | import io.quarkus.deployment.annotations.BuildProducer; 11 | import io.quarkus.deployment.annotations.BuildStep; 12 | import io.quarkus.deployment.builditem.CombinedIndexBuildItem; 13 | 14 | /** 15 | * Add support for the Datadog Meter Registry. Note that the registry may not 16 | * be available at deployment time for some projects: Avoid direct class 17 | * references. 18 | */ 19 | public class DatadogRegistryProcessor { 20 | static final String REGISTRY_CLASS_NAME = "io.micrometer.datadog.DatadogMeterRegistry"; 21 | static final Class REGISTRY_CLASS = MicrometerRecorder.getClassForName(REGISTRY_CLASS_NAME); 22 | 23 | static class DatadogEnabled implements BooleanSupplier { 24 | MicrometerConfig mConfig; 25 | 26 | public boolean getAsBoolean() { 27 | return REGISTRY_CLASS != null && mConfig.checkRegistryEnabledWithDefault(mConfig.export.datadog); 28 | } 29 | } 30 | 31 | @BuildStep(onlyIf = DatadogEnabled.class) 32 | MicrometerRegistryProviderBuildItem createDatadogRegistry(CombinedIndexBuildItem index, 33 | BuildProducer additionalBeans) { 34 | 35 | // Add the Datadog Registry Producer 36 | additionalBeans.produce(AdditionalBeanBuildItem.builder() 37 | .addBeanClass(DatadogMeterRegistryProvider.class) 38 | .setUnremovable().build()); 39 | 40 | // Include the DatadogMeterRegistry in a possible CompositeMeterRegistry 41 | return new MicrometerRegistryProviderBuildItem(REGISTRY_CLASS); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/export/JmxRegistryProcessor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.function.BooleanSupplier; 4 | 5 | import org.jboss.logging.Logger; 6 | 7 | import dev.ebullient.micrometer.deployment.MicrometerRegistryProviderBuildItem; 8 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 9 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig; 10 | import dev.ebullient.micrometer.runtime.export.JmxMeterRegistryProvider; 11 | import io.quarkus.arc.deployment.AdditionalBeanBuildItem; 12 | import io.quarkus.deployment.annotations.BuildProducer; 13 | import io.quarkus.deployment.annotations.BuildStep; 14 | import io.quarkus.deployment.builditem.CombinedIndexBuildItem; 15 | import io.quarkus.deployment.pkg.steps.NativeBuild; 16 | 17 | /** 18 | * Add support for the Jmx Meter Registry. Note that the registry may not 19 | * be available at deployment time for some projects: Avoid direct class 20 | * references. 21 | */ 22 | public class JmxRegistryProcessor { 23 | private static final Logger log = Logger.getLogger(JmxRegistryProcessor.class); 24 | 25 | static final String REGISTRY_CLASS_NAME = "io.micrometer.jmx.JmxMeterRegistry"; 26 | static final Class REGISTRY_CLASS = MicrometerRecorder.getClassForName(REGISTRY_CLASS_NAME); 27 | 28 | static class JmxEnabled implements BooleanSupplier { 29 | MicrometerConfig mConfig; 30 | 31 | public boolean getAsBoolean() { 32 | return REGISTRY_CLASS != null && mConfig.checkRegistryEnabledWithDefault(mConfig.export.jmx); 33 | } 34 | } 35 | 36 | @BuildStep(onlyIf = { NativeBuild.class, JmxEnabled.class }) 37 | MicrometerRegistryProviderBuildItem createJmxRegistry(CombinedIndexBuildItem index) { 38 | log.info("JMX Meter Registry does not support running in native mode."); 39 | return null; 40 | } 41 | 42 | /** Jmx does not work with GraalVM */ 43 | @BuildStep(onlyIf = JmxEnabled.class, onlyIfNot = NativeBuild.class) 44 | MicrometerRegistryProviderBuildItem createJmxRegistry(CombinedIndexBuildItem index, 45 | BuildProducer additionalBeans) { 46 | 47 | // Add the Jmx Registry Producer 48 | additionalBeans.produce(AdditionalBeanBuildItem.builder() 49 | .addBeanClass(JmxMeterRegistryProvider.class) 50 | .setUnremovable().build()); 51 | 52 | // Include the JmxMeterRegistry in a possible CompositeMeterRegistry 53 | return new MicrometerRegistryProviderBuildItem(REGISTRY_CLASS); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/export/PrometheusRegistryProcessor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.function.BooleanSupplier; 4 | 5 | import org.jboss.logging.Logger; 6 | 7 | import dev.ebullient.micrometer.deployment.MicrometerRegistryProviderBuildItem; 8 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 9 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig; 10 | import dev.ebullient.micrometer.runtime.config.PrometheusConfig; 11 | import dev.ebullient.micrometer.runtime.export.PrometheusMeterRegistryProvider; 12 | import dev.ebullient.micrometer.runtime.export.PrometheusRecorder; 13 | import io.quarkus.arc.deployment.AdditionalBeanBuildItem; 14 | import io.quarkus.deployment.annotations.BuildProducer; 15 | import io.quarkus.deployment.annotations.BuildStep; 16 | import io.quarkus.deployment.annotations.ExecutionTime; 17 | import io.quarkus.deployment.annotations.Record; 18 | import io.quarkus.vertx.http.deployment.RouteBuildItem; 19 | 20 | /** 21 | * Add support for the Prometheus Meter Registry. Note that the registry may not 22 | * be available at deployment time for some projects: Avoid direct class 23 | * references. 24 | */ 25 | public class PrometheusRegistryProcessor { 26 | private static final Logger log = Logger.getLogger(PrometheusRegistryProcessor.class); 27 | 28 | static final String REGISTRY_CLASS_NAME = "io.micrometer.prometheus.PrometheusMeterRegistry"; 29 | static final Class REGISTRY_CLASS = MicrometerRecorder.getClassForName(REGISTRY_CLASS_NAME); 30 | 31 | public static class PrometheusEnabled implements BooleanSupplier { 32 | MicrometerConfig mConfig; 33 | 34 | public boolean getAsBoolean() { 35 | return REGISTRY_CLASS != null && mConfig.checkRegistryEnabledWithDefault(mConfig.export.prometheus); 36 | } 37 | } 38 | 39 | @BuildStep(onlyIf = PrometheusEnabled.class) 40 | MicrometerRegistryProviderBuildItem createPrometheusRegistry(BuildProducer additionalBeans) { 41 | 42 | // Add the Prometheus Registry Producer 43 | additionalBeans.produce(AdditionalBeanBuildItem.builder() 44 | .addBeanClass(PrometheusMeterRegistryProvider.class) 45 | .setUnremovable().build()); 46 | 47 | // Include the PrometheusMeterRegistry in a possible CompositeMeterRegistry 48 | return new MicrometerRegistryProviderBuildItem(REGISTRY_CLASS); 49 | } 50 | 51 | @BuildStep(onlyIf = PrometheusEnabled.class) 52 | @Record(value = ExecutionTime.STATIC_INIT) 53 | void createPrometheusRoute(BuildProducer routes, 54 | MicrometerConfig mConfig, 55 | PrometheusRecorder recorder) { 56 | 57 | PrometheusConfig pConfig = mConfig.export.prometheus; 58 | log.debug("PROMETHEUS CONFIG: " + pConfig); 59 | 60 | // Exact match for resources matched to the root path 61 | routes.produce(new RouteBuildItem(recorder.route(pConfig.path), recorder.getHandler())); 62 | 63 | // Match paths that begin with the deployment path 64 | String matchPath = pConfig.path + (pConfig.path.endsWith("/") ? "*" : "/*"); 65 | routes.produce(new RouteBuildItem(recorder.route(matchPath), recorder.getHandler())); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /deployment/src/main/java/dev/ebullient/micrometer/deployment/export/StackdriverRegistryProcessor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.function.BooleanSupplier; 4 | 5 | import org.jboss.logging.Logger; 6 | 7 | import dev.ebullient.micrometer.deployment.MicrometerRegistryProviderBuildItem; 8 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 9 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig; 10 | import dev.ebullient.micrometer.runtime.export.StackdriverMeterRegistryProvider; 11 | import io.quarkus.arc.deployment.AdditionalBeanBuildItem; 12 | import io.quarkus.deployment.annotations.BuildProducer; 13 | import io.quarkus.deployment.annotations.BuildStep; 14 | import io.quarkus.deployment.builditem.CombinedIndexBuildItem; 15 | import io.quarkus.deployment.pkg.steps.NativeBuild; 16 | 17 | /** 18 | * Add support for the Stackdriver Meter Registry. Note that the registry may not 19 | * be available at deployment time for some projects: Avoid direct class 20 | * references. 21 | */ 22 | public class StackdriverRegistryProcessor { 23 | private static final Logger log = Logger.getLogger(StackdriverRegistryProcessor.class); 24 | 25 | static final String REGISTRY_CLASS_NAME = "io.micrometer.stackdriver.StackdriverMeterRegistry"; 26 | static final Class REGISTRY_CLASS = MicrometerRecorder.getClassForName(REGISTRY_CLASS_NAME); 27 | 28 | static class StackdriverEnabled implements BooleanSupplier { 29 | MicrometerConfig mConfig; 30 | 31 | public boolean getAsBoolean() { 32 | return REGISTRY_CLASS != null && mConfig.checkRegistryEnabledWithDefault(mConfig.export.stackdriver); 33 | } 34 | } 35 | 36 | @BuildStep(onlyIf = { NativeBuild.class, StackdriverEnabled.class }) 37 | MicrometerRegistryProviderBuildItem createStackdriverRegistry(CombinedIndexBuildItem index) { 38 | log.info("Stackdriver does not support running in native mode."); 39 | return null; 40 | } 41 | 42 | /** Stackdriver does not work with GraalVM */ 43 | @BuildStep(onlyIf = StackdriverEnabled.class, onlyIfNot = NativeBuild.class) 44 | MicrometerRegistryProviderBuildItem createStackdriverRegistry(CombinedIndexBuildItem index, 45 | BuildProducer additionalBeans) { 46 | 47 | // Add the Stackdriver Registry Producer 48 | additionalBeans.produce(AdditionalBeanBuildItem.builder() 49 | .addBeanClass(StackdriverMeterRegistryProvider.class) 50 | .setUnremovable().build()); 51 | 52 | // Include the StackdriverMeterRegistry in a possible CompositeMeterRegistry 53 | return new MicrometerRegistryProviderBuildItem(REGISTRY_CLASS); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/GlobalDefaultDisabledTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.jboss.shrinkwrap.api.ShrinkWrap; 6 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.RegisterExtension; 10 | 11 | import io.micrometer.core.instrument.MeterRegistry; 12 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 13 | import io.quarkus.test.QuarkusUnitTest; 14 | import io.restassured.RestAssured; 15 | 16 | /** 17 | * Should not have any registered MeterRegistry objects when micrometer is disabled 18 | */ 19 | public class GlobalDefaultDisabledTest { 20 | 21 | @RegisterExtension 22 | static final QuarkusUnitTest config = new QuarkusUnitTest() 23 | .withConfigurationResource("test-logging.properties") 24 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 25 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 26 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); 27 | 28 | @Inject 29 | MeterRegistry registry; 30 | 31 | @Test 32 | public void testMeterRegistryPresent() { 33 | // Composite Meter Registry 34 | Assertions.assertNotNull(registry, "A registry should be configured"); 35 | Assertions.assertTrue(registry instanceof CompositeMeterRegistry, 36 | "Injected registry should be a CompositeMeterRegistry, was " + registry.getClass().getName()); 37 | } 38 | 39 | @Test 40 | public void testNoPrometheusEndpoint() { 41 | // Micrometer is enabled, prometheus is not. 42 | RestAssured.when().get("/prometheus").then().statusCode(404); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/MetricsFromMetricsFactoryTestCase.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import javax.inject.Inject; 6 | 7 | import org.jboss.shrinkwrap.api.ShrinkWrap; 8 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import dev.ebullient.micrometer.runtime.MicrometerMetricsFactory; 14 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig; 15 | import dev.ebullient.test.MeasureThis; 16 | import io.micrometer.core.instrument.FunctionCounter; 17 | import io.micrometer.core.instrument.Gauge; 18 | import io.micrometer.core.instrument.Meter; 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | import io.micrometer.core.instrument.Timer; 21 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 22 | import io.micrometer.core.instrument.search.Search; 23 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 24 | import io.quarkus.test.QuarkusUnitTest; 25 | 26 | public class MetricsFromMetricsFactoryTestCase { 27 | 28 | @RegisterExtension 29 | static QuarkusUnitTest runner = new QuarkusUnitTest() 30 | .withConfigurationResource("test-logging.properties") 31 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 32 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 33 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 34 | .addClass(MeasureThis.class)); 35 | 36 | @Inject 37 | MeterRegistry meterRegistry; 38 | 39 | @Inject 40 | MicrometerConfig config; 41 | 42 | @Test 43 | public void testMetricFactoryCreatedMetrics() throws Exception { 44 | ((CompositeMeterRegistry) meterRegistry).add(new SimpleMeterRegistry()); 45 | MicrometerMetricsFactory factory = new MicrometerMetricsFactory(config, meterRegistry); 46 | MeasureThis.registerMetrics().accept(factory); 47 | 48 | Meter count_me = Search.in(meterRegistry).name("count.me").meter(); 49 | Assertions.assertTrue(FunctionCounter.class.isInstance(count_me)); 50 | MeasureThis.counter.increment(); 51 | Assertions.assertEquals(MeasureThis.counter.doubleValue(), ((FunctionCounter) count_me).count()); 52 | 53 | Meter gauge_me = Search.in(meterRegistry).name("gauge.supplier").meter(); 54 | Assertions.assertTrue(Gauge.class.isInstance(gauge_me)); 55 | MeasureThis.gauge.increment(); 56 | Assertions.assertEquals(MeasureThis.gauge.doubleValue(), ((Gauge) gauge_me).value()); 57 | 58 | Meter runnable = Search.in(meterRegistry).name("time.runnable").meter(); 59 | Assertions.assertTrue(Timer.class.isInstance(runnable)); 60 | MeasureThis.wrappedRunnable.run(); 61 | Assertions.assertEquals(MeasureThis.runnableCount.longValue(), ((Timer) runnable).count(), 62 | "Runnable invocation count should match"); 63 | 64 | Meter callable = Search.in(meterRegistry).name("time.callable").meter(); 65 | Assertions.assertTrue(Timer.class.isInstance(callable)); 66 | MeasureThis.wrappedCallable.call(); 67 | Assertions.assertEquals(MeasureThis.callableCount.longValue(), ((Timer) callable).count(), 68 | "Callable invocation count should match"); 69 | 70 | Meter supplier = Search.in(meterRegistry).name("time.supplier").meter(); 71 | Assertions.assertTrue(Timer.class.isInstance(supplier)); 72 | MeasureThis.wrappedSupplier.get(); 73 | Assertions.assertEquals(MeasureThis.supplierCount.longValue(), ((Timer) supplier).count(), 74 | "Supplier invocation count should match"); 75 | 76 | Meter recorder = Search.in(meterRegistry).name("time.recorder").meter(); 77 | Assertions.assertTrue(Timer.class.isInstance(recorder)); 78 | MeasureThis.timeRecorder.update(10, TimeUnit.MINUTES); 79 | Assertions.assertEquals(1L, ((Timer) recorder).count(), 80 | "Recorder invocation count should match"); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/MicrometerDisabledTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment; 2 | 3 | import javax.enterprise.inject.spi.DeploymentException; 4 | import javax.inject.Inject; 5 | 6 | import org.jboss.shrinkwrap.api.ShrinkWrap; 7 | import org.jboss.shrinkwrap.api.asset.StringAsset; 8 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import io.micrometer.core.instrument.MeterRegistry; 14 | import io.quarkus.test.QuarkusUnitTest; 15 | 16 | /** 17 | * Should not have any registered MeterRegistry objects when micrometer is disabled 18 | */ 19 | public class MicrometerDisabledTest { 20 | 21 | @RegisterExtension 22 | static final QuarkusUnitTest config = new QuarkusUnitTest() 23 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 24 | .addAsResource(new StringAsset("quarkus.micrometer.enabled=false"), "application.properties")) 25 | .assertException(t -> { 26 | Assertions.assertEquals(DeploymentException.class, t.getClass()); 27 | }); 28 | 29 | @Inject 30 | MeterRegistry registry; 31 | 32 | @Test 33 | public void testNoMeterRegistry() { 34 | //Should not be reached: dump what was injected if it somehow passed 35 | Assertions.assertNull(registry, "A MeterRegistry should not be found/injected when micrometer is disabled"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/binder/MpMetricNamingTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.binder; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.jboss.shrinkwrap.api.ShrinkWrap; 6 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.RegisterExtension; 10 | 11 | import dev.ebullient.test.MpColorResource; 12 | import io.micrometer.core.instrument.MeterRegistry; 13 | import io.quarkus.test.QuarkusUnitTest; 14 | 15 | public class MpMetricNamingTest { 16 | 17 | @RegisterExtension 18 | static final QuarkusUnitTest config = new QuarkusUnitTest() 19 | .withConfigurationResource("test-logging.properties") 20 | .overrideConfigKey("quarkus.micrometer.binder.mp-metrics.enabled", "true") 21 | .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "false") 22 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 23 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 24 | .addClass(MpColorResource.class)); 25 | 26 | @Inject 27 | MeterRegistry registry; 28 | 29 | @Inject 30 | MpColorResource colors; 31 | 32 | @Test 33 | public void testAnnotatedMeterNames() { 34 | colors.blue(); 35 | colors.red(); 36 | colors.green(); 37 | colors.yellow(); 38 | 39 | Assertions.assertNotNull( 40 | registry.find("dev.ebullient.test.MpColorResource.red").counter()); 41 | Assertions.assertNotNull( 42 | registry.find("dev.ebullient.test.MpColorResource.blueCount").counter()); 43 | Assertions.assertNotNull( 44 | registry.find("greenCount").counter()); 45 | Assertions.assertNotNull( 46 | registry.find("yellow").gauge()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/AllRegistriesDisabledTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.jboss.shrinkwrap.api.ShrinkWrap; 6 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.RegisterExtension; 10 | 11 | import io.micrometer.core.instrument.MeterRegistry; 12 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 13 | import io.quarkus.test.QuarkusUnitTest; 14 | import io.restassured.RestAssured; 15 | 16 | /** 17 | * Should not have any registered MeterRegistry objects when micrometer is disabled 18 | */ 19 | public class AllRegistriesDisabledTest { 20 | 21 | @RegisterExtension 22 | static final QuarkusUnitTest config = new QuarkusUnitTest() 23 | .withConfigurationResource("test-logging.properties") 24 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 25 | .overrideConfigKey("quarkus.micrometer.export.datadog.enabled", "false") 26 | .overrideConfigKey("quarkus.micrometer.export.jmx.enabled", "false") 27 | .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "false") 28 | .overrideConfigKey("quarkus.micrometer.export.stackdriver.enabled", "false") 29 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); 30 | 31 | @Inject 32 | MeterRegistry registry; 33 | 34 | @Test 35 | public void testMeterRegistryPresent() { 36 | // Composite Meter Registry 37 | Assertions.assertNotNull(registry, "A registry should be configured"); 38 | Assertions.assertEquals(CompositeMeterRegistry.class, registry.getClass(), "Should be CompositeMeterRegistry"); 39 | } 40 | 41 | @Test 42 | public void testNoPrometheusEndpoint() { 43 | // Micrometer is enabled, prometheus is not. 44 | RestAssured.when().get("/prometheus").then().statusCode(404); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/DatadogEnabledInvalidTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.jboss.shrinkwrap.api.ShrinkWrap; 6 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.RegisterExtension; 10 | 11 | import io.micrometer.core.instrument.MeterRegistry; 12 | import io.micrometer.core.instrument.config.validate.ValidationException; 13 | import io.quarkus.test.QuarkusUnitTest; 14 | 15 | public class DatadogEnabledInvalidTest { 16 | @RegisterExtension 17 | static final QuarkusUnitTest config = new QuarkusUnitTest() 18 | .withConfigurationResource("test-logging.properties") 19 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 20 | .overrideConfigKey("quarkus.micrometer.export.datadog.enabled", "true") 21 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 22 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 23 | .addClass(DatadogRegistryProcessor.REGISTRY_CLASS)) 24 | .assertException(t -> { 25 | Assertions.assertEquals(ValidationException.class.getName(), t.getClass().getName()); 26 | }); 27 | 28 | @Inject 29 | MeterRegistry registry; 30 | 31 | @Test 32 | public void testMeterRegistryPresent() { 33 | Assertions.fail("Runtime should not have initialized with missing apiKey"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/DatadogEnabledTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.Set; 4 | 5 | import javax.inject.Inject; 6 | 7 | import org.jboss.shrinkwrap.api.ShrinkWrap; 8 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 14 | import io.micrometer.core.instrument.MeterRegistry; 15 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 16 | import io.quarkus.test.QuarkusUnitTest; 17 | 18 | public class DatadogEnabledTest { 19 | static final String REGISTRY_CLASS_NAME = "io.micrometer.datadog.DatadogMeterRegistry"; 20 | static final Class REGISTRY_CLASS = MicrometerRecorder.getClassForName(REGISTRY_CLASS_NAME); 21 | 22 | @RegisterExtension 23 | static final QuarkusUnitTest config = new QuarkusUnitTest() 24 | .withConfigurationResource("test-logging.properties") 25 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 26 | .overrideConfigKey("quarkus.micrometer.export.datadog.enabled", "true") 27 | .overrideConfigKey("quarkus.micrometer.export.datadog.publish", "false") 28 | .overrideConfigKey("quarkus.micrometer.export.datadog.apiKey", "dummy") 29 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 30 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 31 | .addClass(DatadogRegistryProcessor.REGISTRY_CLASS)); 32 | 33 | @Inject 34 | MeterRegistry registry; 35 | 36 | @Test 37 | public void testMeterRegistryPresent() { 38 | // Datadog is enabled (alone, all others disabled) 39 | Assertions.assertNotNull(registry, "A registry should be configured"); 40 | Set subRegistries = ((CompositeMeterRegistry) registry).getRegistries(); 41 | Assertions.assertEquals(REGISTRY_CLASS, subRegistries.iterator().next().getClass(), "Should be DatadogMeterRegistry"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/JmxEnabledTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.Set; 4 | 5 | import javax.inject.Inject; 6 | 7 | import org.jboss.shrinkwrap.api.ShrinkWrap; 8 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 14 | import io.micrometer.core.instrument.MeterRegistry; 15 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 16 | import io.quarkus.test.QuarkusUnitTest; 17 | 18 | public class JmxEnabledTest { 19 | static final String REGISTRY_CLASS_NAME = "io.micrometer.jmx.JmxMeterRegistry"; 20 | static final Class REGISTRY_CLASS = MicrometerRecorder.getClassForName(REGISTRY_CLASS_NAME); 21 | 22 | @RegisterExtension 23 | static final QuarkusUnitTest config = new QuarkusUnitTest() 24 | .withConfigurationResource("test-logging.properties") 25 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 26 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 27 | .overrideConfigKey("quarkus.micrometer.export.jmx.enabled", "true") 28 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 29 | .addClass(JmxRegistryProcessor.REGISTRY_CLASS)); 30 | 31 | @Inject 32 | MeterRegistry registry; 33 | 34 | @Test 35 | public void testMeterRegistryPresent() { 36 | // Jmx is enabled (alone, all others disabled) 37 | Assertions.assertNotNull(registry, "A registry should be configured"); 38 | Set subRegistries = ((CompositeMeterRegistry) registry).getRegistries(); 39 | Assertions.assertEquals(REGISTRY_CLASS, subRegistries.iterator().next().getClass(), "Should be JmxMeterRegistry"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/PrometheusEnabledTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.Set; 4 | 5 | import javax.inject.Inject; 6 | 7 | import org.jboss.shrinkwrap.api.ShrinkWrap; 8 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import io.micrometer.core.instrument.MeterRegistry; 14 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 15 | import io.micrometer.prometheus.PrometheusMeterRegistry; 16 | import io.quarkus.test.QuarkusUnitTest; 17 | 18 | public class PrometheusEnabledTest { 19 | @RegisterExtension 20 | static final QuarkusUnitTest config = new QuarkusUnitTest() 21 | .withConfigurationResource("test-logging.properties") 22 | .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true") 23 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 24 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 25 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 26 | .addClass(PrometheusRegistryProcessor.REGISTRY_CLASS)); 27 | 28 | @Inject 29 | MeterRegistry registry; 30 | 31 | @Inject 32 | PrometheusMeterRegistry promRegistry; 33 | 34 | @Test 35 | public void testMeterRegistryPresent() { 36 | // Prometheus is enabled (only registry) 37 | Assertions.assertNotNull(registry, "A registry should be configured"); 38 | Set subRegistries = ((CompositeMeterRegistry) registry).getRegistries(); 39 | PrometheusMeterRegistry subPromRegistry = (PrometheusMeterRegistry) subRegistries.iterator().next(); 40 | Assertions.assertEquals(PrometheusMeterRegistry.class, subPromRegistry.getClass(), "Should be PrometheusMeterRegistry"); 41 | Assertions.assertEquals(subPromRegistry, promRegistry, 42 | "The only MeterRegistry should be the same bean as the PrometheusMeterRegistry"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/SecondPrometheusProvider.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import javax.enterprise.inject.Produces; 4 | import javax.inject.Singleton; 5 | 6 | import io.micrometer.core.instrument.Clock; 7 | import io.micrometer.prometheus.PrometheusConfig; 8 | import io.micrometer.prometheus.PrometheusMeterRegistry; 9 | import io.prometheus.client.CollectorRegistry; 10 | 11 | @Singleton 12 | public class SecondPrometheusProvider { 13 | @Produces 14 | @Singleton 15 | public PrometheusMeterRegistry registry(CollectorRegistry collectorRegistry, Clock clock) { 16 | return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT, collectorRegistry, clock); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/SecondPrometheusTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.Set; 4 | 5 | import javax.inject.Inject; 6 | 7 | import org.jboss.shrinkwrap.api.ShrinkWrap; 8 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import io.micrometer.core.instrument.MeterRegistry; 14 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 15 | import io.quarkus.test.QuarkusUnitTest; 16 | 17 | public class SecondPrometheusTest { 18 | @RegisterExtension 19 | static final QuarkusUnitTest config = new QuarkusUnitTest() 20 | .withConfigurationResource("test-logging.properties") 21 | .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true") 22 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 23 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 24 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 25 | .addClass(PrometheusRegistryProcessor.REGISTRY_CLASS) 26 | .addClass(SecondPrometheusProvider.class)); 27 | 28 | @Inject 29 | MeterRegistry registry; 30 | 31 | @Test 32 | public void testMeterRegistryPresent() { 33 | // We want a composite that contains both registries. 34 | Assertions.assertNotNull(registry, "A registry should be configured"); 35 | Set subRegistries = ((CompositeMeterRegistry) registry).getRegistries(); 36 | Assertions.assertEquals(2, subRegistries.size(), "Should be two sub registries"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/StackdriverEnabledInvalidTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.jboss.shrinkwrap.api.ShrinkWrap; 6 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.RegisterExtension; 10 | 11 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 12 | import io.micrometer.core.instrument.MeterRegistry; 13 | import io.micrometer.core.instrument.config.validate.ValidationException; 14 | import io.quarkus.test.QuarkusUnitTest; 15 | 16 | public class StackdriverEnabledInvalidTest { 17 | static final String REGISTRY_CLASS_NAME = "io.micrometer.stackdriver.StackdriverMeterRegistry"; 18 | static final Class REGISTRY_CLASS = MicrometerRecorder.getClassForName(REGISTRY_CLASS_NAME); 19 | 20 | @RegisterExtension 21 | static final QuarkusUnitTest config = new QuarkusUnitTest() 22 | .withConfigurationResource("test-logging.properties") 23 | .overrideConfigKey("quarkus.micrometer.export.stackdriver.enabled", "true") 24 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 25 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 26 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 27 | .addClass(StackdriverRegistryProcessor.REGISTRY_CLASS)) 28 | .assertException(t -> { 29 | Assertions.assertEquals(ValidationException.class.getName(), t.getClass().getName()); 30 | }); 31 | 32 | @Inject 33 | MeterRegistry registry; 34 | 35 | @Test 36 | public void testMeterRegistryPresent() { 37 | // Stackdriver is enabled (alone, all others disabled) 38 | Assertions.assertNotNull(registry, "A registry should be configured"); 39 | Assertions.assertTrue(REGISTRY_CLASS.equals(registry.getClass()), "Should be StackdriverMeterRegistry"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/deployment/export/StackdriverEnabledTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.deployment.export; 2 | 3 | import java.util.Set; 4 | 5 | import javax.inject.Inject; 6 | 7 | import org.jboss.shrinkwrap.api.ShrinkWrap; 8 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 14 | import io.micrometer.core.instrument.MeterRegistry; 15 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 16 | import io.quarkus.test.QuarkusUnitTest; 17 | 18 | public class StackdriverEnabledTest { 19 | static final String REGISTRY_CLASS_NAME = "io.micrometer.stackdriver.StackdriverMeterRegistry"; 20 | static final Class REGISTRY_CLASS = MicrometerRecorder.getClassForName(REGISTRY_CLASS_NAME); 21 | 22 | @RegisterExtension 23 | static final QuarkusUnitTest config = new QuarkusUnitTest() 24 | .withConfigurationResource("test-logging.properties") 25 | .overrideConfigKey("quarkus.micrometer.export.stackdriver.enabled", "true") 26 | .overrideConfigKey("quarkus.micrometer.export.stackdriver.publish", "false") 27 | .overrideConfigKey("quarkus.micrometer.export.stackdriver.project-id", "myproject") 28 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") 29 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 30 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 31 | .addClass(StackdriverRegistryProcessor.REGISTRY_CLASS)); 32 | 33 | @Inject 34 | MeterRegistry registry; 35 | 36 | @Test 37 | public void testMeterRegistryPresent() { 38 | // Stackdriver is enabled (alone, all others disabled) 39 | Assertions.assertNotNull(registry, "A registry should be configured"); 40 | Set subRegistries = ((CompositeMeterRegistry) registry).getRegistries(); 41 | Assertions.assertEquals(REGISTRY_CLASS, subRegistries.iterator().next().getClass(), 42 | "Should be StackdriverMeterRegistry"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/MpMetricRegistrationTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.eclipse.microprofile.metrics.*; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.RegisterExtension; 9 | 10 | import io.micrometer.core.instrument.MeterRegistry; 11 | import io.quarkus.test.QuarkusUnitTest; 12 | 13 | public class MpMetricRegistrationTest { 14 | @RegisterExtension 15 | static final QuarkusUnitTest config = new QuarkusUnitTest() 16 | .withConfigurationResource("test-logging.properties") 17 | .overrideConfigKey("quarkus.micrometer.binder.mp-metrics.enabled", "true") 18 | .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") 19 | .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false"); 20 | 21 | @Inject 22 | MetricRegistryAdapter mpRegistry; 23 | 24 | @Inject 25 | MeterRegistry registry; 26 | 27 | @Test 28 | public void metricsWithSameMetadata() { 29 | Metadata metadata1 = Metadata.builder().withName("meter").withDescription("description1").build(); 30 | Metadata metadata2 = Metadata.builder().withName("meter").withDescription("description1").build(); 31 | 32 | MeterAdapter meter1 = (MeterAdapter) mpRegistry.meter(metadata1); 33 | MeterAdapter meter2 = (MeterAdapter) mpRegistry.meter(metadata2); 34 | 35 | Assertions.assertSame(meter1, meter2); 36 | } 37 | 38 | @Test 39 | public void metricsWithDifferentType() { 40 | Metadata metadata1 = Metadata.builder().withName("metric1") 41 | .withDescription("description1").build(); 42 | Metadata metadata2 = Metadata.builder().withName("metric1") 43 | .withDescription("description2").build(); 44 | 45 | mpRegistry.histogram(metadata1); 46 | 47 | Assertions.assertThrows(IllegalArgumentException.class, () -> { 48 | mpRegistry.meter(metadata2); 49 | }); 50 | } 51 | 52 | @Test 53 | public void wrongTypeInMetadata() { 54 | Metadata metadata1 = Metadata.builder().withName("metric1") 55 | .withDescription("description1").build(); 56 | 57 | Metadata metadata2 = Metadata.builder() 58 | .withName("metric1") 59 | .withType(MetricType.COUNTER) 60 | .build(); 61 | 62 | Assertions.assertThrows(IllegalArgumentException.class, () -> { 63 | mpRegistry.histogram(metadata2); 64 | }); 65 | } 66 | 67 | @Test 68 | public void descriptionChanged() { 69 | Metadata metadata1 = Metadata.builder().withName("metric1") 70 | .withDescription("description1").build(); 71 | Metadata metadata2 = Metadata.builder().withName("metric1") 72 | .withDescription("description2").build(); 73 | 74 | // harmless re-registration 75 | mpRegistry.histogram(metadata1); 76 | HistogramAdapter histogram = (HistogramAdapter) mpRegistry.histogram(metadata1); 77 | 78 | Assertions.assertEquals("description1", histogram.getMeter().getId().getDescription(), 79 | "Description should match first set value"); 80 | } 81 | 82 | @Test 83 | public void metricsWithSameName() { 84 | int cmSize = mpRegistry.constructedMeters.size(); 85 | int mdSize = mpRegistry.metadataMap.size(); 86 | Metadata metadata1 = Metadata.builder().withName("mycounter").withDescription("description1").build(); 87 | 88 | CounterAdapter counter1 = (CounterAdapter) mpRegistry.counter(metadata1); 89 | CounterAdapter counter2 = (CounterAdapter) mpRegistry.counter("mycounter", new Tag("color", "blue")); 90 | 91 | Assertions.assertNotEquals(counter1, counter2); 92 | Assertions.assertEquals("description1", counter1.getMeter().getId().getDescription(), 93 | "Description should match shared value"); 94 | Assertions.assertEquals("description1", counter2.getMeter().getId().getDescription(), 95 | "Description should match shared value"); 96 | 97 | mpRegistry.remove("mycounter"); 98 | 99 | Assertions.assertEquals(cmSize, mpRegistry.constructedMeters.size(), 100 | "Both counters should have been removed"); 101 | Assertions.assertEquals(mdSize, mpRegistry.metadataMap.size(), 102 | "mycounter metadata should have been removed"); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/test/MeasureThis.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.test; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.atomic.LongAdder; 5 | import java.util.function.Consumer; 6 | import java.util.function.Supplier; 7 | 8 | import io.quarkus.runtime.metrics.MetricsFactory; 9 | 10 | public class MeasureThis { 11 | public static LongAdder counter = new LongAdder(); 12 | public static LongAdder gauge = new LongAdder(); 13 | 14 | public static LongAdder runnableCount = new LongAdder(); 15 | public static LongAdder callableCount = new LongAdder(); 16 | public static LongAdder supplierCount = new LongAdder(); 17 | 18 | public static Runnable wrappedRunnable; 19 | public static Callable wrappedCallable; 20 | public static Supplier wrappedSupplier; 21 | 22 | public static MetricsFactory.TimeRecorder timeRecorder; 23 | 24 | public static Consumer registerMetrics() { 25 | MeasureThis mt = new MeasureThis(); 26 | return new Consumer() { 27 | @Override 28 | public void accept(MetricsFactory metricsFactory) { 29 | metricsFactory.builder("count.me") 30 | .buildCounter(MeasureThis.counter::longValue); 31 | metricsFactory.builder("gauge.supplier") 32 | .buildGauge(MeasureThis.gauge::doubleValue); 33 | 34 | MeasureThis.wrappedRunnable = metricsFactory.builder("time.runnable") 35 | .buildTimer(new Runnable() { 36 | @Override 37 | public void run() { 38 | runnableCount.increment(); 39 | } 40 | }); 41 | 42 | MeasureThis.wrappedCallable = metricsFactory.builder("time.callable") 43 | .buildTimer(new Callable() { 44 | @Override 45 | public Long call() throws Exception { 46 | callableCount.increment(); 47 | return callableCount.sum(); 48 | } 49 | }); 50 | 51 | MeasureThis.wrappedSupplier = metricsFactory.builder("time.supplier") 52 | .buildTimer(new Supplier() { 53 | @Override 54 | public Long get() { 55 | supplierCount.increment(); 56 | return supplierCount.sum(); 57 | } 58 | }); 59 | 60 | MeasureThis.timeRecorder = metricsFactory.builder("time.recorder") 61 | .buildTimer(); 62 | } 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /deployment/src/test/java/dev/ebullient/test/MpColorResource.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.test; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | 5 | import org.eclipse.microprofile.metrics.annotation.Counted; 6 | import org.eclipse.microprofile.metrics.annotation.Gauge; 7 | 8 | @ApplicationScoped 9 | public class MpColorResource { 10 | @Counted 11 | public void red() { 12 | // ... 13 | } 14 | 15 | @Counted(name = "blueCount") 16 | public void blue() { 17 | // ... 18 | } 19 | 20 | @Counted(name = "greenCount", absolute = true) 21 | public void green() { 22 | // ... 23 | } 24 | 25 | @Gauge(absolute = true, unit = "jellybeans") 26 | public long yellow() { 27 | return 0L; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /deployment/src/test/resources/test-logging.properties: -------------------------------------------------------------------------------- 1 | quarkus.log.category."dev.ebullient".level=DEBUG 2 | quarkus.log.category."io.quarkus.bootstrap".level=INFO 3 | quarkus.log.category."io.quarkus.arc".level=DEBUG 4 | quarkus.log.category."io.netty".level=INFO 5 | -------------------------------------------------------------------------------- /integration-tests/jmx/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | dev.ebullient.quarkus-micrometer-extension 8 | quarkus-micrometer-integration-tests 9 | ${revision} 10 | ../pom.xml 11 | 12 | 13 | 14 | 15 | ../../ 16 | 17 | 18 | quarkus-micrometer-integration-tests-jmx 19 | Micrometer Quarkus extension - JMX Integration Tests 20 | 21 | 22 | 23 | io.micrometer 24 | micrometer-core 25 | 26 | 27 | 28 | io.micrometer 29 | micrometer-registry-jmx 30 | 31 | 32 | 33 | 34 | 35 | 36 | maven-surefire-plugin 37 | 38 | 39 | org.jboss.logmanager.LogManager 40 | DEBUG 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /integration-tests/jmx/src/main/java/dev/ebullient/it/micrometer/jmx/CustomConfiguration.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.jmx; 2 | 3 | import java.util.Arrays; 4 | 5 | import javax.annotation.Priority; 6 | import javax.enterprise.inject.Produces; 7 | import javax.inject.Singleton; 8 | import javax.interceptor.Interceptor; 9 | 10 | import dev.ebullient.micrometer.runtime.MeterFilterConstraint; 11 | import io.micrometer.core.instrument.Tag; 12 | import io.micrometer.core.instrument.config.MeterFilter; 13 | import io.micrometer.jmx.JmxMeterRegistry; 14 | 15 | @Singleton 16 | @Priority(Interceptor.Priority.APPLICATION - 100) 17 | public class CustomConfiguration { 18 | 19 | @Produces 20 | @Singleton 21 | @MeterFilterConstraint(applyTo = JmxMeterRegistry.class) 22 | public MeterFilter configurePrometheusRegistries() { 23 | return MeterFilter.commonTags(Arrays.asList( 24 | Tag.of("registry", "jmx"))); 25 | } 26 | 27 | @Produces 28 | @Singleton 29 | @MeterFilterConstraint(applyTo = CustomConfiguration.class) 30 | public MeterFilter configureNonexistantRegistries() { 31 | return MeterFilter.commonTags(Arrays.asList( 32 | Tag.of("tag", "class-should-not-match"))); 33 | } 34 | 35 | @Produces 36 | @Singleton 37 | public MeterFilter configureAllRegistries() { 38 | return MeterFilter.commonTags(Arrays.asList( 39 | Tag.of("env", "test"))); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /integration-tests/jmx/src/main/java/dev/ebullient/it/micrometer/jmx/MessageResource.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.jmx; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.util.Set; 5 | 6 | import javax.management.*; 7 | import javax.ws.rs.GET; 8 | import javax.ws.rs.Path; 9 | import javax.ws.rs.PathParam; 10 | 11 | import io.micrometer.core.instrument.MeterRegistry; 12 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 13 | 14 | @Path("/message") 15 | public class MessageResource { 16 | 17 | private final MeterRegistry registry; 18 | private final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); 19 | 20 | public MessageResource(MeterRegistry registry) { 21 | this.registry = registry; 22 | } 23 | 24 | @GET 25 | public String message() { 26 | CompositeMeterRegistry compositeMeterRegistry = (CompositeMeterRegistry) registry; 27 | Set subRegistries = compositeMeterRegistry.getRegistries(); 28 | return subRegistries.iterator().next().getClass().getName(); 29 | } 30 | 31 | @GET 32 | @Path("fail") 33 | public String fail() { 34 | throw new RuntimeException("Failed on purpose"); 35 | } 36 | 37 | @GET 38 | @Path("item/{id}") 39 | public String item(@PathParam("id") String id) { 40 | return "return message with id " + id; 41 | } 42 | 43 | @GET 44 | @Path("mbeans") 45 | public String metrics() throws IntrospectionException, InstanceNotFoundException, ReflectionException { 46 | Set mbeans = mBeanServer.queryNames(null, null); 47 | StringBuilder sb = new StringBuilder(); 48 | for (ObjectName mbean : mbeans) { 49 | if (mbean.getCanonicalName().startsWith("metrics:name=http")) { 50 | sb.append(mbean.getCanonicalName()).append("\n"); 51 | } 52 | } 53 | return sb.toString(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /integration-tests/jmx/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | quarkus.log.category."dev.ebullient".level=DEBUG 3 | quarkus.log.category."io.quarkus.bootstrap".level=INFO 4 | quarkus.log.category."io.quarkus.netty".level=INFO 5 | quarkus.log.category."io.quarkus.resteasy.runtime".level=INFO 6 | 7 | quarkus.log.category."io.netty".level=INFO 8 | quarkus.log.category."org.apache".level=INFO 9 | 10 | quarkus.micrometer.export.prometheus.enabled=false 11 | quarkus.micrometer.registry-enabled-default=true 12 | -------------------------------------------------------------------------------- /integration-tests/jmx/src/test/java/dev/ebullient/it/micrometer/jmx/JmxMetricsRegistryTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.jmx; 2 | 3 | import static io.restassured.RestAssured.given; 4 | import static org.hamcrest.CoreMatchers.containsString; 5 | import static org.hamcrest.CoreMatchers.not; 6 | 7 | import org.junit.jupiter.api.MethodOrderer; 8 | import org.junit.jupiter.api.Order; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.TestMethodOrder; 11 | 12 | import io.quarkus.test.junit.QuarkusTest; 13 | 14 | /** 15 | * Test functioning prometheus endpoint. 16 | * Use test execution order to ensure one http server request measurement 17 | * is present when the endpoint is scraped. 18 | */ 19 | @QuarkusTest 20 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 21 | class JmxMetricsRegistryTest { 22 | 23 | @Test 24 | @Order(1) 25 | void testRegistryInjection() { 26 | given() 27 | .when().get("/message") 28 | .then() 29 | .statusCode(200) 30 | .body(containsString("io.micrometer.jmx.JmxMeterRegistry")); 31 | } 32 | 33 | @Test 34 | @Order(2) 35 | void testUnknownUrl() { 36 | given() 37 | .when().get("/messsage/notfound") 38 | .then() 39 | .statusCode(404); 40 | } 41 | 42 | @Test 43 | @Order(3) 44 | void testServerError() { 45 | given() 46 | .when().get("/message/fail") 47 | .then() 48 | .statusCode(500); 49 | } 50 | 51 | @Test 52 | @Order(3) 53 | void testPathParameter() { 54 | given() 55 | .when().get("/message/item/123") 56 | .then() 57 | .statusCode(200); 58 | } 59 | 60 | @Test 61 | @Order(10) 62 | void testJmxReporter() { 63 | 64 | given() 65 | .when().get("/message/mbeans") 66 | .then() 67 | .statusCode(200) 68 | 69 | // JMX endpoint is returning a subset of mbean objects to inspect 70 | // hierarchical naming means all tags are present: registry=jmx, and env=test 71 | 72 | // Generic connection statistic 73 | .body(containsString("metrics:name=httpServerConnections.env.test.registry.jmx.statistic")) 74 | 75 | .body(containsString( 76 | "metrics:name=httpServerRequests.env.test.method.GET.outcome.CLIENT_ERROR.registry.jmx.status.404.uri.NOT_FOUND")) 77 | .body(containsString( 78 | "metrics:name=httpServerRequests.env.test.method.GET.outcome.SERVER_ERROR.registry.jmx.status.500.uri./message/fail")) 79 | 80 | .body(containsString( 81 | "metrics:name=httpServerRequests.env.test.method.GET.outcome.SUCCESS.registry.jmx.status.200.uri./message")) 82 | .body(containsString( 83 | "metrics:name=httpServerRequests.env.test.method.GET.outcome.SUCCESS.registry.jmx.status.200.uri./message/item/{id}")) 84 | 85 | // this was defined by a tag to a non-matching registry, and should not be found 86 | .body(not(containsString("class-should-not-match"))); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /integration-tests/mp-metrics/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | dev.ebullient.quarkus-micrometer-extension 8 | quarkus-micrometer-integration-tests 9 | ${revision} 10 | ../pom.xml 11 | 12 | 13 | 14 | 15 | ../../ 16 | 17 | 18 | quarkus-micrometer-integration-tests-mp-metrics 19 | Micrometer Quarkus extension - MP Metrics Integration Tests 20 | 21 | 22 | 23 | org.eclipse.microprofile.metrics 24 | microprofile-metrics-api 25 | 26 | 27 | 28 | io.micrometer 29 | micrometer-core 30 | 31 | 32 | 33 | io.micrometer 34 | micrometer-registry-prometheus 35 | 36 | 37 | 38 | 39 | 40 | 41 | maven-surefire-plugin 42 | 43 | 44 | org.jboss.logmanager.LogManager 45 | DEBUG 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /integration-tests/mp-metrics/src/main/java/dev/ebullient/it/micrometer/mpmetrics/CountedInstance.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.mpmetrics; 2 | 3 | import javax.enterprise.context.RequestScoped; 4 | 5 | import org.eclipse.microprofile.metrics.annotation.Counted; 6 | 7 | @RequestScoped 8 | @Counted(description = "called for each discovered prime number") 9 | public class CountedInstance { 10 | 11 | CountedInstance() { 12 | } 13 | 14 | public void countPrimes() { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integration-tests/mp-metrics/src/main/java/dev/ebullient/it/micrometer/mpmetrics/InjectedInstance.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.mpmetrics; 2 | 3 | import java.util.Random; 4 | 5 | import javax.enterprise.inject.Produces; 6 | import javax.inject.Inject; 7 | import javax.inject.Singleton; 8 | 9 | import org.eclipse.microprofile.metrics.Counter; 10 | import org.eclipse.microprofile.metrics.Gauge; 11 | import org.eclipse.microprofile.metrics.Histogram; 12 | import org.eclipse.microprofile.metrics.annotation.Metric; 13 | 14 | @Singleton 15 | public class InjectedInstance { 16 | Random r = new Random(); 17 | 18 | @Inject 19 | @Metric(name = "notPrime", description = "Count the number of not prime numbers") 20 | Counter count; 21 | 22 | @Inject 23 | @Metric(name = "valueRange", description = "Aggregate checked values") 24 | Histogram histogram; 25 | 26 | @Produces 27 | @Metric(name = "passiveInjection", description = "This is ignored: @Produces + @Metric") 28 | Gauge createGauge = new Gauge() { 29 | @Override 30 | public Integer getValue() { 31 | return r.nextInt(); 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /integration-tests/mp-metrics/src/main/java/dev/ebullient/it/micrometer/mpmetrics/PrimeResource.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.mpmetrics; 2 | 3 | import java.util.concurrent.atomic.LongAccumulator; 4 | 5 | import javax.inject.Inject; 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Path; 8 | import javax.ws.rs.Produces; 9 | 10 | import org.eclipse.microprofile.metrics.MetricUnits; 11 | import org.eclipse.microprofile.metrics.annotation.*; 12 | import org.jboss.resteasy.annotations.jaxrs.PathParam; 13 | 14 | @Path("/prime") 15 | public class PrimeResource { 16 | 17 | private LongAccumulator highestPrimeSoFar = new LongAccumulator(Long::max, 2); 18 | 19 | @Inject 20 | CountedInstance countedResource; 21 | 22 | InjectedInstance injectedMetrics; 23 | 24 | @Metered 25 | PrimeResource(InjectedInstance injectedMetrics) { 26 | this.injectedMetrics = injectedMetrics; 27 | } 28 | 29 | @GET 30 | @Path("/{number}") 31 | @Produces("text/plain") 32 | @ConcurrentGauge(description = "active calls to checkIfPrime") 33 | public String checkIfPrime(@PathParam long number) { 34 | String result = checkPrime(number); 35 | 36 | if (result.length() > 0) { 37 | injectedMetrics.count.inc(); 38 | return result; 39 | } 40 | 41 | countedResource.countPrimes(); 42 | highestPrimeSoFar.accumulate(number); 43 | return number + " is prime."; 44 | } 45 | 46 | // This method is both counted and timed. The counted annotation should be suppressed/removed 47 | @Timed(name = "checksTimer", description = "Measure how long it takes to perform the primality test.", unit = MetricUnits.MILLISECONDS) 48 | @Counted 49 | String checkPrime(long number) { 50 | injectedMetrics.histogram.update(number); 51 | 52 | if (number < 1) { 53 | return "Only natural numbers can be prime numbers."; 54 | } 55 | if (number == 1) { 56 | return "1 is not prime."; 57 | } 58 | if (number == 2) { 59 | return "2 is prime."; 60 | } 61 | if (number % 2 == 0) { 62 | return number + " is not prime, it is divisible by 2."; 63 | } 64 | for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) { 65 | if (number % i == 0) { 66 | return number + " is not prime, is divisible by " + i + "."; 67 | } 68 | } 69 | return ""; 70 | } 71 | 72 | @Gauge(name = "highestPrimeNumberSoFar", unit = MetricUnits.NONE, description = "Highest prime number so far.") 73 | public Long highestPrimeNumberSoFar() { 74 | return highestPrimeSoFar.get(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /integration-tests/mp-metrics/src/main/java/dev/ebullient/it/micrometer/mpmetrics/RenameMeterFilterProducer.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.mpmetrics; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import javax.enterprise.inject.Produces; 7 | import javax.inject.Singleton; 8 | 9 | import io.micrometer.core.instrument.Meter; 10 | import io.micrometer.core.instrument.Tag; 11 | import io.micrometer.core.instrument.config.MeterFilter; 12 | 13 | @Singleton 14 | public class RenameMeterFilterProducer { 15 | static String targetMetric = PrimeResource.class.getName() + ".highestPrimeNumberSoFar"; 16 | 17 | @Produces 18 | @Singleton 19 | MeterFilter renameMeterFilter() { 20 | return new MeterFilter() { 21 | @Override 22 | public Meter.Id map(Meter.Id id) { 23 | if (id.getName().equals(targetMetric)) { 24 | List tags = id.getTags().stream().filter(x -> !"scope".equals(x.getKey())) 25 | .collect(Collectors.toList()); 26 | return id.withName("highestPrimeNumberSoFar").replaceTags(tags); 27 | } 28 | return id; 29 | } 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /integration-tests/mp-metrics/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.log.category."dev.ebullient".level=DEBUG 2 | quarkus.log.category."dev.ebullient.micrometer.runtime.binder.vertx".level=INFO 3 | 4 | quarkus.log.category."io.quarkus.bootstrap".level=INFO 5 | quarkus.log.category."io.quarkus.netty".level=INFO 6 | quarkus.log.category."io.quarkus.resteasy".level=INFO 7 | 8 | quarkus.log.category."io.netty".level=INFO 9 | quarkus.log.category."io.restassured".level=INFO 10 | quarkus.log.category."org.apache".level=INFO 11 | quarkus.log.category."org.jboss.resteasy".level=INFO 12 | 13 | quarkus.micrometer.binder.mp-metrics.enabled=true 14 | -------------------------------------------------------------------------------- /integration-tests/mp-metrics/src/test/java/dev/ebullient/it/micrometer/mpmetrics/MPMetricsIT.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.mpmetrics; 2 | 3 | import io.quarkus.test.junit.NativeImageTest; 4 | 5 | @NativeImageTest 6 | class MPMetricsIT extends MPMetricsTest { 7 | } 8 | -------------------------------------------------------------------------------- /integration-tests/mp-metrics/src/test/java/dev/ebullient/it/micrometer/mpmetrics/MPMetricsTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.mpmetrics; 2 | 3 | import static io.restassured.RestAssured.given; 4 | import static org.hamcrest.CoreMatchers.containsString; 5 | import static org.hamcrest.CoreMatchers.not; 6 | 7 | import org.junit.jupiter.api.MethodOrderer; 8 | import org.junit.jupiter.api.Order; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.TestMethodOrder; 11 | 12 | import io.quarkus.test.junit.QuarkusTest; 13 | 14 | @QuarkusTest 15 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 16 | class MPMetricsTest { 17 | 18 | @Test 19 | @Order(1) 20 | void callPrimeGen_1() { 21 | given() 22 | .when().get("/prime/31") 23 | .then() 24 | .statusCode(200); 25 | } 26 | 27 | @Test 28 | @Order(2) 29 | void callPrimeGen_2() { 30 | given() 31 | .when().get("/prime/33") 32 | .then() 33 | .statusCode(200); 34 | } 35 | 36 | @Test 37 | @Order(3) 38 | void callPrimeGen_3() { 39 | given() 40 | .when().get("/prime/887") 41 | .then() 42 | .statusCode(200); 43 | } 44 | 45 | @Test 46 | @Order(4) 47 | void validateMetricsOutput_1() { 48 | given() 49 | .when().get("/prometheus") 50 | .then() 51 | .statusCode(200) 52 | 53 | // Prometheus body has ALL THE THINGS in no particular order 54 | 55 | // PrimeResource 56 | .body(containsString( 57 | "dev_ebullient_it_micrometer_mpmetrics_PrimeResource_PrimeResource_total{scope=\"application\",} 1.0")) 58 | .body(containsString( 59 | "dev_ebullient_it_micrometer_mpmetrics_CountedInstance_CountedInstance_total{scope=\"application\",} 2.0")) 60 | 61 | // number of concurrent requests at time of sample 62 | .body(containsString( 63 | "dev_ebullient_it_micrometer_mpmetrics_PrimeResource_checkIfPrime{scope=\"application\",} 0.0")) 64 | .body(containsString( 65 | "dev_ebullient_it_micrometer_mpmetrics_CountedInstance_countPrimes_total{scope=\"application\",} 2.0")) 66 | .body(containsString( 67 | "highestPrimeNumberSoFar 887.0")) 68 | 69 | // the counter associated with a timed method should have been removed 70 | .body(not(containsString("dev_ebullient_it_micrometer_mpmetrics_PrimeResource_checkPrime"))); 71 | } 72 | 73 | @Test 74 | @Order(5) 75 | void callPrimeGen_4() { 76 | given() 77 | .when().get("/prime/900") 78 | .then() 79 | .statusCode(200); 80 | } 81 | 82 | @Test 83 | @Order(6) 84 | void validateMetricsOutput_2() { 85 | given() 86 | .when().get("/prometheus") 87 | .then() 88 | .log().body() 89 | .statusCode(200) 90 | 91 | // Prometheus body has ALL THE THINGS in no particular order 92 | .body(containsString( 93 | "dev_ebullient_it_micrometer_mpmetrics_CountedInstance_countPrimes_total{scope=\"application\",} 2.0")) 94 | .body(containsString( 95 | "highestPrimeNumberSoFar 887.0")) 96 | .body(containsString( 97 | "dev_ebullient_it_micrometer_mpmetrics_InjectedInstance_notPrime_total{scope=\"application\",}")); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 4.0.0 20 | 21 | 22 | dev.ebullient.quarkus-micrometer-extension 23 | quarkus-micrometer-build 24 | ${revision} 25 | ../build/pom.xml 26 | 27 | 28 | quarkus-micrometer-integration-tests 29 | Micrometer Quarkus extension - Integration Tests 30 | pom 31 | 32 | 33 | true 34 | true 35 | 36 | 37 | 38 | jmx 39 | prometheus 40 | mp-metrics 41 | 42 | 43 | 44 | 45 | ${project.groupId} 46 | quarkus-micrometer 47 | ${project.version} 48 | 49 | 50 | 51 | 52 | io.quarkus 53 | quarkus-resteasy 54 | 55 | 56 | io.quarkus 57 | quarkus-resteasy-jackson 58 | 59 | 60 | 61 | 62 | io.quarkus 63 | quarkus-junit5 64 | test 65 | 66 | 67 | io.rest-assured 68 | rest-assured 69 | test 70 | 71 | 72 | org.jboss.logging 73 | commons-logging-jboss-logging 74 | test 75 | 76 | 77 | 78 | 79 | 80 | 81 | io.quarkus 82 | quarkus-maven-plugin 83 | ${quarkus.version} 84 | 85 | 86 | 87 | build 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | native 98 | 99 | 100 | native 101 | 102 | 103 | 104 | 105 | native 106 | 107 | 108 | 109 | 110 | 111 | io.quarkus 112 | quarkus-maven-plugin 113 | ${quarkus.version} 114 | 115 | 116 | 117 | native-image 118 | 119 | 120 | true 121 | 122 | 123 | 124 | 125 | 126 | 127 | maven-failsafe-plugin 128 | ${surefire-plugin.version} 129 | 130 | 131 | 132 | integration-test 133 | verify 134 | 135 | 136 | 137 | ${project.build.directory}/${project.build.finalName}-runner 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /integration-tests/prometheus/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | dev.ebullient.quarkus-micrometer-extension 8 | quarkus-micrometer-integration-tests 9 | ${revision} 10 | ../pom.xml 11 | 12 | 13 | 14 | 15 | ../../ 16 | 17 | 18 | quarkus-micrometer-integration-tests-prometheus 19 | Micrometer Quarkus extension - Prometheus Integration Tests 20 | 21 | 22 | 23 | io.quarkus 24 | quarkus-hibernate-orm-panache 25 | 26 | 27 | 28 | io.quarkus 29 | quarkus-jdbc-h2 30 | 31 | 32 | 33 | io.micrometer 34 | micrometer-core 35 | 36 | 37 | 38 | io.micrometer 39 | micrometer-registry-prometheus 40 | 41 | 42 | 43 | io.quarkus 44 | quarkus-test-h2 45 | test 46 | 47 | 48 | 49 | 50 | 51 | 52 | maven-surefire-plugin 53 | 54 | 55 | org.jboss.logmanager.LogManager 56 | DEBUG 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /integration-tests/prometheus/src/main/java/dev/ebullient/it/micrometer/prometheus/CustomConfiguration.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.prometheus; 2 | 3 | import java.util.Arrays; 4 | 5 | import javax.annotation.Priority; 6 | import javax.enterprise.inject.Produces; 7 | import javax.inject.Singleton; 8 | import javax.interceptor.Interceptor; 9 | 10 | import dev.ebullient.micrometer.runtime.MeterFilterConstraint; 11 | import io.micrometer.core.instrument.Clock; 12 | import io.micrometer.core.instrument.Tag; 13 | import io.micrometer.core.instrument.config.MeterFilter; 14 | import io.micrometer.prometheus.PrometheusConfig; 15 | import io.micrometer.prometheus.PrometheusMeterRegistry; 16 | import io.prometheus.client.CollectorRegistry; 17 | 18 | @Singleton 19 | @Priority(Interceptor.Priority.APPLICATION - 100) 20 | public class CustomConfiguration { 21 | 22 | @Produces 23 | @Singleton 24 | @MeterFilterConstraint(applyTo = PrometheusMeterRegistry.class) 25 | public MeterFilter configurePrometheusRegistries() { 26 | return MeterFilter.commonTags(Arrays.asList( 27 | Tag.of("registry", "prometheus"))); 28 | } 29 | 30 | @Produces 31 | @Singleton 32 | @MeterFilterConstraint(applyTo = CustomConfiguration.class) 33 | public MeterFilter configureNonexistantRegistries() { 34 | return MeterFilter.commonTags(Arrays.asList( 35 | Tag.of("tag", "class-should-not-match"))); 36 | } 37 | 38 | @Produces 39 | @Singleton 40 | public MeterFilter configureAllRegistries() { 41 | return MeterFilter.commonTags(Arrays.asList( 42 | Tag.of("env", "test"))); 43 | } 44 | 45 | /** 46 | * Produce a custom prometheus configuration that is isolated/separate from 47 | * the application (and won't be connected to the Quarkus configured application 48 | * endpoint). 49 | */ 50 | @Produces 51 | @Singleton 52 | public PrometheusMeterRegistry registry(CollectorRegistry collectorRegistry, Clock clock) { 53 | return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT, collectorRegistry, clock); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /integration-tests/prometheus/src/main/java/dev/ebullient/it/micrometer/prometheus/Fruit.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.prometheus; 2 | 3 | import javax.persistence.Entity; 4 | 5 | import io.quarkus.hibernate.orm.panache.PanacheEntity; 6 | 7 | @Entity 8 | public class Fruit extends PanacheEntity { 9 | public String name; 10 | 11 | public Fruit(String name) { 12 | this.name = name; 13 | } 14 | 15 | public Fruit() { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /integration-tests/prometheus/src/main/java/dev/ebullient/it/micrometer/prometheus/FruitResource.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.prometheus; 2 | 3 | import java.util.List; 4 | 5 | import javax.transaction.Transactional; 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Path; 8 | 9 | import io.quarkus.hibernate.orm.panache.PanacheQuery; 10 | 11 | @Path("/fruit") 12 | public class FruitResource { 13 | 14 | @GET 15 | @Path("create") 16 | @Transactional 17 | public void trigger() { 18 | Fruit apple = new Fruit("apple"); 19 | Fruit pear = new Fruit("pear"); 20 | Fruit banana = new Fruit("banana"); 21 | 22 | Fruit.persist(apple, pear, banana); 23 | } 24 | 25 | @GET 26 | @Path("all") 27 | public void retrieveAll() { 28 | PanacheQuery query = Fruit.find( 29 | "select name from Fruit"); 30 | List all = query.list(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /integration-tests/prometheus/src/main/java/dev/ebullient/it/micrometer/prometheus/MessageResource.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.prometheus; 2 | 3 | import javax.ws.rs.GET; 4 | import javax.ws.rs.Path; 5 | import javax.ws.rs.PathParam; 6 | 7 | import io.micrometer.core.instrument.MeterRegistry; 8 | 9 | @Path("/message") 10 | public class MessageResource { 11 | 12 | private final MeterRegistry registry; 13 | 14 | public MessageResource(MeterRegistry registry) { 15 | this.registry = registry; 16 | } 17 | 18 | @GET 19 | public String message() { 20 | return registry.getClass().getName(); 21 | } 22 | 23 | @GET 24 | @Path("fail") 25 | public String fail() { 26 | throw new RuntimeException("Failed on purpose"); 27 | } 28 | 29 | @GET 30 | @Path("item/{id}") 31 | public String item(@PathParam("id") String id) { 32 | return "return message with id " + id; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /integration-tests/prometheus/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.log.category."dev.ebullient".level=DEBUG 2 | quarkus.log.category."io.quarkus.bootstrap".level=INFO 3 | quarkus.log.category."io.quarkus.netty".level=INFO 4 | quarkus.log.category."io.quarkus.resteasy.runtime".level=INFO 5 | 6 | quarkus.log.category."io.netty".level=INFO 7 | quarkus.log.category."org.apache".level=INFO 8 | 9 | quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect 10 | quarkus.hibernate-orm.database.generation=drop-and-create 11 | quarkus.hibernate-orm.statistics=true 12 | 13 | quarkus.datasource.db-kind=h2 14 | quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test 15 | quarkus.datasource.jdbc.max-size=8 16 | quarkus.datasource.jdbc.min-size=2 17 | 18 | quarkus.micrometer.binder.vertx.ignore-patterns=/fruit/create 19 | -------------------------------------------------------------------------------- /integration-tests/prometheus/src/test/java/dev/ebullient/it/micrometer/prometheus/PrometheusMetricsRegistryIT.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.prometheus; 2 | 3 | import io.quarkus.test.junit.NativeImageTest; 4 | 5 | /** 6 | * tests that application.properties is read from src/main/resources when running native image tests 7 | * 8 | * This does not necessarily belong here, but main and test-extension have a lot of existing 9 | * config that would need to be duplicated, so it is here out of convenience. 10 | */ 11 | @NativeImageTest 12 | class PrometheusMetricsRegistryIT extends PrometheusMetricsRegistryTest { 13 | } 14 | -------------------------------------------------------------------------------- /integration-tests/prometheus/src/test/java/dev/ebullient/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.prometheus; 2 | 3 | import static io.restassured.RestAssured.given; 4 | import static org.hamcrest.CoreMatchers.containsString; 5 | import static org.hamcrest.CoreMatchers.not; 6 | 7 | import org.junit.jupiter.api.MethodOrderer; 8 | import org.junit.jupiter.api.Order; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.TestMethodOrder; 11 | 12 | import io.quarkus.test.junit.QuarkusTest; 13 | 14 | /** 15 | * Test functioning prometheus endpoint. 16 | * Use test execution order to ensure one http server request measurement 17 | * is present when the endpoint is scraped. 18 | */ 19 | @QuarkusTest 20 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 21 | class PrometheusMetricsRegistryTest { 22 | 23 | @Test 24 | @Order(1) 25 | void testRegistryInjection() { 26 | given() 27 | .when().get("/message") 28 | .then() 29 | .statusCode(200) 30 | .body(containsString("io.micrometer.core.instrument.composite.CompositeMeterRegistry")); 31 | } 32 | 33 | @Test 34 | @Order(2) 35 | void testUnknownUrl() { 36 | given() 37 | .when().get("/messsage/notfound") 38 | .then() 39 | .statusCode(404); 40 | } 41 | 42 | @Test 43 | @Order(3) 44 | void testServerError() { 45 | given() 46 | .when().get("/message/fail") 47 | .then() 48 | .statusCode(500); 49 | } 50 | 51 | @Test 52 | @Order(3) 53 | void testPathParameter() { 54 | given() 55 | .when().get("/message/item/123") 56 | .then() 57 | .statusCode(200); 58 | } 59 | 60 | @Test 61 | @Order(4) 62 | void testPanacheCalls() { 63 | given() 64 | .when().get("/fruit/create") 65 | .then() 66 | .statusCode(204); 67 | 68 | given() 69 | .when().get("/fruit/all") 70 | .then() 71 | .statusCode(204); 72 | } 73 | 74 | @Test 75 | @Order(10) 76 | void testPrometheusScrapeEndpoint() { 77 | given() 78 | .when().get("/prometheus") 79 | .then() 80 | .log().body() 81 | .statusCode(200) 82 | 83 | // Prometheus body has ALL THE THINGS in no particular order 84 | .body(containsString("jvm_info")) 85 | 86 | .body(containsString("registry=\"prometheus\"")) 87 | .body(containsString("env=\"test\"")) 88 | 89 | .body(containsString("http_server_requests")) 90 | 91 | .body(containsString("status=\"404\"")) 92 | .body(containsString("uri=\"NOT_FOUND\"")) 93 | .body(containsString("outcome=\"CLIENT_ERROR\"")) 94 | 95 | .body(containsString("status=\"500\"")) 96 | .body(containsString("uri=\"/message/fail\"")) 97 | .body(containsString("outcome=\"SERVER_ERROR\"")) 98 | 99 | .body(containsString("status=\"200\"")) 100 | .body(containsString("uri=\"/message\"")) 101 | .body(containsString("uri=\"/message/item/{id}\"")) 102 | .body(containsString("outcome=\"SUCCESS\"")) 103 | 104 | // Verify Hibernate Metrics 105 | .body(containsString( 106 | "hibernate_sessions_open_total{entityManagerFactory=\"default\",env=\"test\",registry=\"prometheus\",} 2.0")) 107 | .body(containsString( 108 | "hibernate_sessions_closed_total{entityManagerFactory=\"default\",env=\"test\",registry=\"prometheus\",} 2.0")) 109 | .body(containsString( 110 | "hibernate_connections_obtained_total{entityManagerFactory=\"default\",env=\"test\",registry=\"prometheus\",}")) 111 | .body(containsString( 112 | "hibernate_entities_inserts_total{entityManagerFactory=\"default\",env=\"test\",registry=\"prometheus\",} 3.0")) 113 | .body(containsString( 114 | "hibernate_flushes_total{entityManagerFactory=\"default\",env=\"test\",registry=\"prometheus\",} 2.0")) 115 | 116 | // this was defined by a tag to a non-matching registry, and should not be found 117 | .body(not(containsString("class-should-not-match"))) 118 | 119 | // should not find this ignored uri 120 | .body(not(containsString("uri=\"/fruit/create\""))); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /integration-tests/prometheus/src/test/java/dev/ebullient/it/micrometer/prometheus/TestResources.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.it.micrometer.prometheus; 2 | 3 | import io.quarkus.test.common.QuarkusTestResource; 4 | import io.quarkus.test.h2.H2DatabaseTestResource; 5 | 6 | @QuarkusTestResource(H2DatabaseTestResource.class) 7 | public class TestResources { 8 | } 9 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 4.0.0 20 | 21 | dev.ebullient.quarkus-micrometer-extension 22 | quarkus-micrometer-parent 23 | ${revision} 24 | pom 25 | 26 | Micrometer Quarkus extension - Parent 27 | https://github.com/ebullient/quarkus-micrometer-extension 28 | 29 | 30 | 1.7.0-SNAPSHOT 31 | 32 | 1.7.0.CR2 33 | 1.5.3 34 | 35 | 1.2.1 36 | 37 | 38 | 39 | https://github.com/ebullient/quarkus-micrometer-extension 40 | scm:git:https://github.com/ebullient/quarkus-micrometer-extension.git 41 | scm:git:git@github.com/ebullient/quarkus-micrometer-extension.git 42 | ${project.version} 43 | 44 | 45 | 46 | 47 | github 48 | GitHub ebullient Quarkus micrometer extension Maven Packages 49 | https://maven.pkg.github.com/ebullient/quarkus-micrometer-extension 50 | 51 | 52 | 53 | 54 | GitHub 55 | https://github.com/ebullient/quarkus-micrometer-extension/issues 56 | 57 | 58 | 59 | 60 | The Apache License, Version 2.0 61 | http://www.apache.org/licenses/LICENSE-2.0.txt 62 | repo 63 | 64 | 65 | 66 | 67 | 68 | ebullient 69 | Erin Schnabel 70 | IBM 71 | https://www.ibm.com 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.codehaus.mojo 79 | flatten-maven-plugin 80 | ${flatten-maven-plugin.version} 81 | 82 | true 83 | resolveCiFriendliesOnly 84 | true 85 | 86 | 87 | 88 | flatten 89 | process-resources 90 | 91 | flatten 92 | 93 | 94 | 95 | flatten.clean 96 | clean 97 | 98 | clean 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | build 108 | runtime 109 | deployment 110 | 111 | 112 | integration-tests 113 | 114 | 115 | -------------------------------------------------------------------------------- /runtime/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 4.0.0 20 | 21 | 22 | dev.ebullient.quarkus-micrometer-extension 23 | quarkus-micrometer-build 24 | ${revision} 25 | ../build/pom.xml 26 | 27 | 28 | quarkus-micrometer 29 | Micrometer Quarkus extension - Runtime 30 | 31 | 32 | 33 | ../ 34 | 35 | 36 | 37 | 38 | io.quarkus 39 | quarkus-core 40 | 41 | 42 | 43 | io.quarkus 44 | quarkus-arc 45 | 46 | 47 | 48 | io.quarkus 49 | quarkus-vertx-http 50 | 51 | 52 | 53 | io.micrometer 54 | micrometer-core 55 | 56 | 57 | 58 | 59 | 60 | io.quarkus 61 | quarkus-resteasy 62 | true 63 | 64 | 65 | 66 | io.quarkus 67 | quarkus-hibernate-orm 68 | true 69 | 70 | 71 | 72 | org.eclipse.microprofile.metrics 73 | microprofile-metrics-api 74 | true 75 | 76 | 77 | 78 | 79 | 80 | io.micrometer 81 | micrometer-registry-datadog 82 | true 83 | 84 | 85 | io.micrometer 86 | micrometer-registry-jmx 87 | true 88 | 89 | 90 | io.micrometer 91 | micrometer-registry-prometheus 92 | true 93 | 94 | 95 | io.micrometer 96 | micrometer-registry-stackdriver 97 | true 98 | 99 | 100 | 101 | 102 | 103 | org.junit.jupiter 104 | junit-jupiter-api 105 | test 106 | 107 | 108 | org.junit.jupiter 109 | junit-jupiter-engine 110 | test 111 | 112 | 113 | org.mockito 114 | mockito-core 115 | test 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | io.quarkus 124 | quarkus-bootstrap-maven-plugin 125 | ${quarkus.version} 126 | 127 | 128 | 129 | extension-descriptor 130 | 131 | 132 | ${project.groupId}:${project.artifactId}-deployment:${project.version} 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/ClockProvider.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime; 2 | 3 | import javax.enterprise.inject.Produces; 4 | import javax.inject.Singleton; 5 | 6 | import io.micrometer.core.instrument.Clock; 7 | import io.quarkus.arc.DefaultBean; 8 | 9 | @Singleton 10 | public class ClockProvider { 11 | @Produces 12 | @Singleton 13 | @DefaultBean 14 | public Clock clock() { 15 | return Clock.SYSTEM; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/CompositeRegistryCreator.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime; 2 | 3 | import static javax.interceptor.Interceptor.Priority.PLATFORM_AFTER; 4 | 5 | import javax.enterprise.inject.Produces; 6 | import javax.inject.Singleton; 7 | 8 | import io.micrometer.core.instrument.MeterRegistry; 9 | import io.micrometer.core.instrument.Metrics; 10 | import io.quarkus.arc.AlternativePriority; 11 | 12 | /** 13 | * @return the single resolveable "root" MeterRegistry 14 | */ 15 | public class CompositeRegistryCreator { 16 | @Produces 17 | @Singleton 18 | @AlternativePriority(PLATFORM_AFTER) 19 | public MeterRegistry produceRootRegistry() { 20 | return Metrics.globalRegistry; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/MeterFilterConstraint.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Repeatable; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import javax.enterprise.util.AnnotationLiteral; 10 | import javax.inject.Qualifier; 11 | 12 | @Qualifier 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD }) 15 | @Repeatable(MeterFilterConstraints.class) 16 | public @interface MeterFilterConstraint { 17 | Class applyTo(); 18 | 19 | final class Literal extends AnnotationLiteral implements MeterFilterConstraint { 20 | private static final long serialVersionUID = 1L; 21 | private final Class clazz; 22 | 23 | public Literal(Class clazz) { 24 | this.clazz = clazz; 25 | } 26 | 27 | @Override 28 | public Class applyTo() { 29 | return clazz; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/MeterFilterConstraints.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD }) 10 | public @interface MeterFilterConstraints { 11 | MeterFilterConstraint[] value(); 12 | } 13 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/HibernateMetricsRecorder.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder; 2 | 3 | import org.hibernate.SessionFactory; 4 | 5 | import io.micrometer.core.instrument.MeterRegistry; 6 | import io.micrometer.core.instrument.Metrics; 7 | import io.micrometer.core.instrument.Tags; 8 | import io.micrometer.core.instrument.binder.jpa.HibernateMetrics; 9 | import io.micrometer.core.instrument.binder.jpa.HibernateQueryMetrics; 10 | import io.quarkus.arc.runtime.BeanContainer; 11 | import io.quarkus.hibernate.orm.runtime.JPAConfig; 12 | import io.quarkus.runtime.annotations.Recorder; 13 | 14 | @Recorder 15 | public class HibernateMetricsRecorder { 16 | 17 | /* RUNTIME_INIT */ 18 | public void registerMetrics(BeanContainer beanContainer) { 19 | MeterRegistry meterRegistry = Metrics.globalRegistry; 20 | 21 | JPAConfig jpaConfig = beanContainer.instance(JPAConfig.class); 22 | for (String puName : jpaConfig.getPersistenceUnits()) { 23 | SessionFactory sessionFactory = jpaConfig.getEntityManagerFactory(puName).unwrap(SessionFactory.class); 24 | if (sessionFactory != null) { 25 | // Configure HibernateMetrics 26 | HibernateMetrics.monitor(meterRegistry, sessionFactory, puName, Tags.empty()); 27 | 28 | // Configure HibernateQueryMetrics 29 | HibernateQueryMetrics.monitor(meterRegistry, sessionFactory, puName, Tags.empty()); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/JvmInfoMetrics.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder; 2 | 3 | import io.micrometer.core.instrument.Gauge; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import io.micrometer.core.instrument.binder.MeterBinder; 6 | 7 | public class JvmInfoMetrics implements MeterBinder { 8 | 9 | @Override 10 | public void bindTo(MeterRegistry registry) { 11 | Gauge.builder("jvm.info", () -> 1L) 12 | .description("JVM version info") 13 | .tags("version", System.getProperty("java.runtime.version", "unknown"), 14 | "vendor", System.getProperty("java.vm.vendor", "unknown"), 15 | "runtime", System.getProperty("java.runtime.name", "unknown")) 16 | .strongReference(true) 17 | .register(registry); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/AnnotatedGaugeAdapter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.eclipse.microprofile.metrics.MetricType; 6 | 7 | import io.micrometer.core.instrument.Gauge; 8 | import io.micrometer.core.instrument.Meter; 9 | import io.micrometer.core.instrument.MeterRegistry; 10 | 11 | public interface AnnotatedGaugeAdapter extends org.eclipse.microprofile.metrics.Gauge, MeterHolder { 12 | String name(); 13 | 14 | String description(); 15 | 16 | String[] tags(); 17 | 18 | String baseUnit(); 19 | 20 | /** Called by MpRegistryAdapter to register the gauge */ 21 | AnnotatedGaugeAdapter register(MetricDescriptor id, MeterRegistry registry); 22 | 23 | MetricDescriptor getId(); 24 | 25 | MpMetadata getMetadata(); 26 | 27 | String getTargetName(); 28 | 29 | /** 30 | * Generic base instance of an AnnotatedGaugeAdapter. 31 | * Generated beans extend this base. 32 | */ 33 | public static abstract class GaugeAdapterImpl implements AnnotatedGaugeAdapter { 34 | final MpMetadata metadata; 35 | final String targetName; 36 | final String[] tags; 37 | 38 | MetricDescriptor id; 39 | Gauge gauge; 40 | 41 | public GaugeAdapterImpl(String name, String description, String targetName, String[] tags) { 42 | this(name, description, targetName, null, tags); 43 | } 44 | 45 | public GaugeAdapterImpl(String name, String description, String targetName, String baseUnit, String[] tags) { 46 | this.metadata = new MpMetadata(name, description, baseUnit, MetricType.GAUGE); 47 | this.targetName = name; 48 | this.tags = tags; 49 | } 50 | 51 | /** Called by MpRegistryAdapter to register the gauge */ 52 | public GaugeAdapterImpl register(MetricDescriptor id, MeterRegistry registry) { 53 | this.id = id; 54 | if (gauge == null || metadata.cleanDirtyMetadata()) { 55 | gauge = io.micrometer.core.instrument.Gauge.builder(metadata.name, this::getValue) 56 | .description(metadata.description()) 57 | .tags(id.tags()) 58 | .baseUnit(metadata.unit()) 59 | .strongReference(true) 60 | .register(registry); 61 | } 62 | return this; 63 | } 64 | 65 | public String name() { 66 | return metadata.name; 67 | } 68 | 69 | public String description() { 70 | return metadata.description; 71 | } 72 | 73 | public String baseUnit() { 74 | return metadata.unit; 75 | } 76 | 77 | public String[] tags() { 78 | return tags; 79 | } 80 | 81 | public Meter getMeter() { 82 | return gauge; 83 | } 84 | 85 | public MetricDescriptor getId() { 86 | return id; 87 | } 88 | 89 | public MpMetadata getMetadata() { 90 | return metadata; 91 | } 92 | 93 | public MetricType getType() { 94 | return MetricType.GAUGE; 95 | } 96 | 97 | public String getTargetName() { 98 | return targetName; 99 | } 100 | 101 | public String toString() { 102 | return this.getClass().getName() 103 | + "[ name=" + id.name 104 | + ", tags=" + Arrays.asList(id.tags) 105 | + ", target=" + targetName 106 | + "]"; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/ConcurrentGaugeImpl.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import java.util.concurrent.atomic.LongAdder; 4 | 5 | import org.eclipse.microprofile.metrics.ConcurrentGauge; 6 | import org.eclipse.microprofile.metrics.MetricType; 7 | 8 | import io.micrometer.core.instrument.Gauge; 9 | import io.micrometer.core.instrument.Meter; 10 | import io.micrometer.core.instrument.MeterRegistry; 11 | 12 | class ConcurrentGaugeImpl implements ConcurrentGauge, MeterHolder { 13 | final LongAdder longAdder = new LongAdder(); 14 | Gauge gauge; 15 | 16 | ConcurrentGaugeImpl register(MpMetadata metadata, MetricDescriptor metricInfo, MeterRegistry registry) { 17 | gauge = io.micrometer.core.instrument.Gauge.builder(metricInfo.name(), longAdder::longValue) 18 | .description(metadata.description()) 19 | .baseUnit(metadata.unit()) 20 | .tags(metricInfo.tags()) 21 | .strongReference(true) 22 | .register(registry); 23 | return this; 24 | } 25 | 26 | @Override 27 | public long getCount() { 28 | return longAdder.longValue(); 29 | } 30 | 31 | /** 32 | * Not supported for micrometer. Min/max values per dropwizard 33 | * would be provided by dropwizard capabilities if enabled. 34 | */ 35 | @Override 36 | public long getMax() { 37 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 38 | } 39 | 40 | /** 41 | * Not supported for micrometer. Min/max values per dropwizard 42 | * would be provided by dropwizard capabilities if enabled. 43 | */ 44 | @Override 45 | public long getMin() { 46 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 47 | } 48 | 49 | @Override 50 | public void inc() { 51 | longAdder.increment(); 52 | } 53 | 54 | @Override 55 | public void dec() { 56 | longAdder.decrement(); 57 | } 58 | 59 | @Override 60 | public Meter getMeter() { 61 | return gauge; 62 | } 63 | 64 | @Override 65 | public MetricType getType() { 66 | return MetricType.CONCURRENT_GAUGE; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/ConcurrentGaugeInterceptor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import javax.annotation.Priority; 4 | import javax.interceptor.AroundConstruct; 5 | import javax.interceptor.AroundInvoke; 6 | import javax.interceptor.AroundTimeout; 7 | import javax.interceptor.Interceptor; 8 | import javax.interceptor.InvocationContext; 9 | 10 | import org.eclipse.microprofile.metrics.MetricType; 11 | import org.eclipse.microprofile.metrics.annotation.ConcurrentGauge; 12 | 13 | @ConcurrentGauge 14 | @Interceptor 15 | @Priority(Interceptor.Priority.LIBRARY_BEFORE + 10) 16 | class ConcurrentGaugeInterceptor { 17 | 18 | // Micrometer meter registry 19 | final MetricRegistryAdapter mpRegistry; 20 | 21 | ConcurrentGaugeInterceptor(MetricRegistryAdapter mpRegistry) { 22 | this.mpRegistry = mpRegistry; 23 | } 24 | 25 | @AroundConstruct 26 | Object cGaugeConstructor(InvocationContext context) throws Exception { 27 | return cGauge(context, context.getConstructor().getDeclaringClass().getSimpleName()); 28 | } 29 | 30 | @AroundInvoke 31 | Object cGaugeMethod(InvocationContext context) throws Exception { 32 | return cGauge(context, context.getMethod().getName()); 33 | } 34 | 35 | @AroundTimeout 36 | Object cGaugeTimeout(InvocationContext context) throws Exception { 37 | return cGauge(context, context.getMethod().getName()); 38 | } 39 | 40 | Object cGauge(InvocationContext context, String methodName) throws Exception { 41 | ConcurrentGauge annotation = MpMetricsRegistryProducer.getAnnotation(context, ConcurrentGauge.class); 42 | if (annotation != null) { 43 | MpMetadata metadata = new MpMetadata(annotation.name().replace("", methodName), 44 | annotation.description().replace("", methodName), 45 | annotation.unit(), 46 | MetricType.CONCURRENT_GAUGE); 47 | 48 | ConcurrentGaugeImpl impl = mpRegistry.interceptorConcurrentGauge(metadata, annotation.tags()); 49 | try { 50 | impl.inc(); 51 | return context.proceed(); 52 | } finally { 53 | impl.dec(); 54 | } 55 | } 56 | return context.proceed(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/CountedInterceptor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import javax.annotation.Priority; 4 | import javax.interceptor.AroundConstruct; 5 | import javax.interceptor.AroundInvoke; 6 | import javax.interceptor.AroundTimeout; 7 | import javax.interceptor.Interceptor; 8 | import javax.interceptor.InvocationContext; 9 | 10 | import org.eclipse.microprofile.metrics.MetricType; 11 | import org.eclipse.microprofile.metrics.annotation.Counted; 12 | 13 | @SuppressWarnings("unused") 14 | @Counted 15 | @Interceptor 16 | @Priority(Interceptor.Priority.LIBRARY_BEFORE + 10) 17 | class CountedInterceptor { 18 | 19 | // Micrometer meter registry 20 | final MetricRegistryAdapter mpRegistry; 21 | 22 | CountedInterceptor(MetricRegistryAdapter mpRegistry) { 23 | this.mpRegistry = mpRegistry; 24 | } 25 | 26 | @AroundConstruct 27 | Object countedConstructor(InvocationContext context) throws Exception { 28 | return increment(context, context.getConstructor().getDeclaringClass().getSimpleName()); 29 | } 30 | 31 | @AroundInvoke 32 | Object countedMethod(InvocationContext context) throws Exception { 33 | return increment(context, context.getMethod().getName()); 34 | } 35 | 36 | @AroundTimeout 37 | Object countedTimeout(InvocationContext context) throws Exception { 38 | return increment(context, context.getMethod().getName()); 39 | } 40 | 41 | Object increment(InvocationContext context, String methodName) throws Exception { 42 | Counted annotation = MpMetricsRegistryProducer.getAnnotation(context, Counted.class); 43 | if (annotation != null) { 44 | MpMetadata metadata = new MpMetadata(annotation.name().replace("", methodName), 45 | annotation.description().replace("", methodName), 46 | annotation.unit(), 47 | MetricType.COUNTER); 48 | 49 | mpRegistry.interceptorCounter(metadata, annotation.tags()).inc(); 50 | } 51 | return context.proceed(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/CounterAdapter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import org.eclipse.microprofile.metrics.MetricType; 4 | 5 | import io.micrometer.core.instrument.Counter; 6 | import io.micrometer.core.instrument.Meter; 7 | import io.micrometer.core.instrument.MeterRegistry; 8 | 9 | class CounterAdapter implements org.eclipse.microprofile.metrics.Counter, MeterHolder { 10 | 11 | Counter counter; 12 | 13 | public CounterAdapter register(MpMetadata metadata, MetricDescriptor descriptor, MeterRegistry registry) { 14 | if (counter == null || metadata.cleanDirtyMetadata()) { 15 | counter = io.micrometer.core.instrument.Counter.builder(descriptor.name()) 16 | .description(metadata.description()) 17 | .baseUnit(metadata.unit()) 18 | .tags(descriptor.tags()) 19 | .register(registry); 20 | } 21 | 22 | return this; 23 | } 24 | 25 | @Override 26 | public void inc() { 27 | counter.increment(); 28 | } 29 | 30 | @Override 31 | public void inc(long l) { 32 | counter.increment((double) l); 33 | } 34 | 35 | @Override 36 | public long getCount() { 37 | return (long) counter.count(); 38 | } 39 | 40 | @Override 41 | public Meter getMeter() { 42 | return counter; 43 | } 44 | 45 | @Override 46 | public MetricType getType() { 47 | return MetricType.COUNTER; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/GaugeAdapter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import java.util.function.Supplier; 4 | import java.util.function.ToDoubleFunction; 5 | 6 | import org.eclipse.microprofile.metrics.Gauge; 7 | import org.eclipse.microprofile.metrics.MetricType; 8 | 9 | import io.micrometer.core.instrument.Meter; 10 | import io.micrometer.core.instrument.MeterRegistry; 11 | 12 | interface GaugeAdapter extends Gauge, MeterHolder { 13 | 14 | GaugeAdapter register(MpMetadata metadata, MetricDescriptor metricInfo, MeterRegistry registry); 15 | 16 | static class DoubleFunctionGauge implements GaugeAdapter { 17 | io.micrometer.core.instrument.Gauge gauge; 18 | 19 | final S obj; 20 | final ToDoubleFunction f; 21 | 22 | DoubleFunctionGauge(S obj, ToDoubleFunction f) { 23 | this.obj = obj; 24 | this.f = f; 25 | } 26 | 27 | public GaugeAdapter register(MpMetadata metadata, MetricDescriptor metricInfo, MeterRegistry registry) { 28 | gauge = io.micrometer.core.instrument.Gauge.builder(metricInfo.name(), obj, f) 29 | .description(metadata.description()) 30 | .tags(metricInfo.tags()) 31 | .baseUnit(metadata.unit()) 32 | .strongReference(true) 33 | .register(registry); 34 | return this; 35 | } 36 | 37 | @Override 38 | public Meter getMeter() { 39 | return gauge; 40 | } 41 | 42 | @Override 43 | public Double getValue() { 44 | return gauge.value(); 45 | } 46 | 47 | @Override 48 | public MetricType getType() { 49 | return MetricType.GAUGE; 50 | } 51 | } 52 | 53 | static class NumberSupplierGauge implements GaugeAdapter { 54 | io.micrometer.core.instrument.Gauge gauge; 55 | final Supplier supplier; 56 | 57 | NumberSupplierGauge(Supplier supplier) { 58 | this.supplier = supplier; 59 | } 60 | 61 | @Override 62 | public GaugeAdapter register(MpMetadata metadata, MetricDescriptor metricInfo, MeterRegistry registry) { 63 | if (gauge == null || metadata.cleanDirtyMetadata()) { 64 | gauge = io.micrometer.core.instrument.Gauge.builder(metricInfo.name(), (Supplier) supplier) 65 | .description(metadata.description()) 66 | .tags(metricInfo.tags()) 67 | .baseUnit(metadata.unit()) 68 | .strongReference(true).register(registry); 69 | } 70 | 71 | return this; 72 | } 73 | 74 | @Override 75 | public Meter getMeter() { 76 | return gauge; 77 | } 78 | 79 | @Override 80 | public T getValue() { 81 | return supplier.get(); 82 | } 83 | 84 | @Override 85 | public MetricType getType() { 86 | return MetricType.GAUGE; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/HistogramAdapter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import org.eclipse.microprofile.metrics.Histogram; 4 | import org.eclipse.microprofile.metrics.MetricType; 5 | import org.eclipse.microprofile.metrics.Snapshot; 6 | 7 | import io.micrometer.core.instrument.DistributionSummary; 8 | import io.micrometer.core.instrument.Meter; 9 | import io.micrometer.core.instrument.MeterRegistry; 10 | 11 | class HistogramAdapter implements Histogram, MeterHolder { 12 | DistributionSummary summary; 13 | 14 | HistogramAdapter register(MpMetadata metadata, MetricDescriptor metricInfo, MeterRegistry registry) { 15 | if (summary == null || metadata.cleanDirtyMetadata()) { 16 | summary = io.micrometer.core.instrument.DistributionSummary.builder(metricInfo.name()) 17 | .description(metadata.description()) 18 | .baseUnit(metadata.unit()) 19 | .tags(metricInfo.tags()) 20 | .register(registry); 21 | } 22 | 23 | return this; 24 | } 25 | 26 | @Override 27 | public void update(int i) { 28 | summary.record(i); 29 | } 30 | 31 | @Override 32 | public void update(long l) { 33 | summary.record(l); 34 | } 35 | 36 | @Override 37 | public long getCount() { 38 | return summary.count(); 39 | } 40 | 41 | /** Not supported. */ 42 | @Override 43 | public Snapshot getSnapshot() { 44 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 45 | } 46 | 47 | @Override 48 | public Meter getMeter() { 49 | return summary; 50 | } 51 | 52 | @Override 53 | public MetricType getType() { 54 | return MetricType.HISTOGRAM; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/InjectedMetricProducer.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import javax.enterprise.inject.Produces; 4 | import javax.enterprise.inject.spi.InjectionPoint; 5 | import javax.inject.Singleton; 6 | 7 | import org.eclipse.microprofile.metrics.*; 8 | import org.eclipse.microprofile.metrics.annotation.Metric; 9 | import org.jboss.logging.Logger; 10 | 11 | /** 12 | * Create default producer methods for {literal @}Inject {literal @}Metric 13 | * annotations requiring {@code Meter}, {@code Timer}, {@code Counter}, 14 | * and {@code Histogram}. 15 | * 16 | * Due to build-time processing, {literal @}Metric annotations always have 17 | * a name value that has been resolved according to MP Metrics naming conventions. 18 | */ 19 | @SuppressWarnings("unused") 20 | @Singleton 21 | class InjectedMetricProducer { 22 | private static final Logger log = Logger.getLogger(InjectedMetricProducer.class); 23 | 24 | // Micrometer meter registry 25 | final MetricRegistryAdapter mpRegistry; 26 | 27 | InjectedMetricProducer(MetricRegistryAdapter mpRegistry) { 28 | this.mpRegistry = mpRegistry; 29 | } 30 | 31 | @Produces 32 | Counter getCounter(InjectionPoint ip) { 33 | Metric metricInfo = ip.getAnnotated().getAnnotation(Metric.class); 34 | return mpRegistry.injectedCounter(metricInfo); 35 | } 36 | 37 | /** 38 | * For a programmatic concurrent gauge, create a gauge around 39 | * a simple implementation that uses a {@code LongAdder}. 40 | * The metrics gathered this way will not be as rich as with the 41 | * {@code LongTimerTask}-based metrics used with the 42 | * {literal @}ConcurrentGauge annotation, but is the best the API 43 | * semantics allow (decrement/increment). 44 | */ 45 | @Produces 46 | ConcurrentGauge getConcurrentGauge(InjectionPoint ip) { 47 | Metric metricInfo = ip.getAnnotated().getAnnotation(Metric.class); 48 | return mpRegistry.injectedConcurrentGauge(metricInfo); 49 | } 50 | 51 | @Produces 52 | Histogram getHistogram(InjectionPoint ip) { 53 | Metric metricInfo = ip.getAnnotated().getAnnotation(Metric.class); 54 | return mpRegistry.injectedHistogram(metricInfo); 55 | } 56 | 57 | @Produces 58 | Meter getMeter(InjectionPoint ip) { 59 | Metric metricInfo = ip.getAnnotated().getAnnotation(Metric.class); 60 | return mpRegistry.injectedMeter(metricInfo); 61 | } 62 | 63 | @Produces 64 | SimpleTimer getSimpleTimer(InjectionPoint ip) { 65 | Metric metricInfo = ip.getAnnotated().getAnnotation(Metric.class); 66 | return mpRegistry.injectedSimpleTimer(metricInfo); 67 | } 68 | 69 | @Produces 70 | Timer getTimer(InjectionPoint ip) { 71 | Metric metricInfo = ip.getAnnotated().getAnnotation(Metric.class); 72 | return mpRegistry.injectedTimer(metricInfo); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/MeterAdapter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import org.eclipse.microprofile.metrics.Meter; 4 | import org.eclipse.microprofile.metrics.MetricType; 5 | 6 | import io.micrometer.core.instrument.Counter; 7 | import io.micrometer.core.instrument.MeterRegistry; 8 | 9 | class MeterAdapter implements Meter, MeterHolder { 10 | Counter counter; 11 | 12 | public MeterAdapter register(MpMetadata metadata, MetricDescriptor descriptor, MeterRegistry registry) { 13 | if (counter == null || metadata.cleanDirtyMetadata()) { 14 | counter = io.micrometer.core.instrument.Counter.builder(descriptor.name()) 15 | .description(metadata.description()) 16 | .baseUnit(metadata.unit()) 17 | .tags(descriptor.tags()) 18 | .register(registry); 19 | } 20 | 21 | return this; 22 | } 23 | 24 | @Override 25 | public void mark() { 26 | counter.increment(); 27 | } 28 | 29 | @Override 30 | public void mark(long l) { 31 | counter.increment(l); 32 | } 33 | 34 | @Override 35 | public long getCount() { 36 | return (long) counter.count(); 37 | } 38 | 39 | @Override 40 | public double getFifteenMinuteRate() { 41 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 42 | } 43 | 44 | @Override 45 | public double getFiveMinuteRate() { 46 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 47 | } 48 | 49 | @Override 50 | public double getMeanRate() { 51 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 52 | } 53 | 54 | @Override 55 | public double getOneMinuteRate() { 56 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 57 | } 58 | 59 | @Override 60 | public io.micrometer.core.instrument.Meter getMeter() { 61 | return counter; 62 | } 63 | 64 | @Override 65 | public MetricType getType() { 66 | return MetricType.METERED; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/MeterHolder.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import org.eclipse.microprofile.metrics.Metric; 4 | import org.eclipse.microprofile.metrics.MetricType; 5 | 6 | import io.micrometer.core.instrument.Meter; 7 | 8 | interface MeterHolder extends Metric { 9 | Meter getMeter(); 10 | 11 | MetricType getType(); 12 | } 13 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/MetricDescriptor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Objects; 7 | 8 | import org.eclipse.microprofile.metrics.MetricID; 9 | import org.eclipse.microprofile.metrics.Tag; 10 | import org.eclipse.microprofile.metrics.annotation.Metric; 11 | 12 | import io.micrometer.core.instrument.Tags; 13 | 14 | class MetricDescriptor { 15 | final String name; 16 | final Tags tags; 17 | ExtendedMetricID metricId = null; 18 | 19 | MetricDescriptor(String name, Tags tags) { 20 | this.name = name; 21 | this.tags = tags; 22 | } 23 | 24 | MetricDescriptor(String name, String... tags) { 25 | this.name = name; 26 | this.tags = Tags.of(tags); 27 | } 28 | 29 | public MetricDescriptor(Metric annotation) { 30 | this.name = annotation.name(); 31 | this.tags = Tags.of(annotation.tags()); 32 | } 33 | 34 | public MetricDescriptor(AnnotatedGaugeAdapter adapter) { 35 | this.name = adapter.name(); 36 | this.tags = Tags.of(adapter.tags()); 37 | } 38 | 39 | public String name() { 40 | return name; 41 | } 42 | 43 | public Tags tags() { 44 | return tags; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object o) { 49 | if (this == o) 50 | return true; 51 | if (o == null || getClass() != o.getClass()) 52 | return false; 53 | MetricDescriptor that = (MetricDescriptor) o; 54 | return Objects.equals(name, that.name) && Objects.equals(tags, that.tags); 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | int result = Objects.hash(name); 60 | result = 31 * result + tags.hashCode(); 61 | return result; 62 | } 63 | 64 | Tag[] convertTags() { 65 | List mpTags = new ArrayList<>(); 66 | tags.stream().forEach(x -> mpTags.add(new Tag(x.getKey(), x.getValue()))); 67 | return mpTags.toArray(new Tag[0]); 68 | } 69 | 70 | public String toString() { 71 | return name + Arrays.asList(tags); 72 | } 73 | 74 | // Deal with ubiquitous MetricID containing nefarious TreeSet and arbitrary 75 | // dips into MP Config 76 | 77 | public MetricDescriptor(MetricID metricID) { 78 | this.name = metricID.getName(); 79 | this.tags = extractTags(metricID.getTagsAsList()); 80 | this.metricId = sanitizeMetricID(metricID, this); 81 | } 82 | 83 | private Tags extractTags(List tagsAsList) { 84 | List list = new ArrayList<>(); 85 | for (Tag t : tagsAsList) { 86 | list.add(io.micrometer.core.instrument.Tag.of(t.getTagName(), t.getTagValue())); 87 | } 88 | return Tags.of(list); 89 | } 90 | 91 | public MetricID toMetricID() { 92 | ExtendedMetricID result = metricId; 93 | if (result == null) { 94 | result = metricId = new ExtendedMetricID(this); 95 | } 96 | return result; 97 | } 98 | 99 | public static ExtendedMetricID sanitizeMetricID(MetricID metricID, MetricDescriptor metricDescriptor) { 100 | if (metricID instanceof ExtendedMetricID) { 101 | return (ExtendedMetricID) metricID; 102 | } else { 103 | return new ExtendedMetricID(metricDescriptor); 104 | } 105 | } 106 | 107 | /** 108 | * Ensure all hashcode/equals comparisons are against MetricDescriptor 109 | * data (not ancillary MP Config additions). 110 | */ 111 | static class ExtendedMetricID extends MetricID { 112 | final MetricDescriptor source; 113 | 114 | public ExtendedMetricID(MetricDescriptor source) { 115 | super(source.name(), source.convertTags()); 116 | this.source = source; 117 | } 118 | 119 | @Override 120 | public boolean equals(Object o) { 121 | if (this == o) 122 | return true; 123 | if (o == null || getClass() != o.getClass()) 124 | return false; 125 | if (!super.equals(o)) 126 | return false; 127 | ExtendedMetricID that = (ExtendedMetricID) o; 128 | return source.equals(that.source); 129 | } 130 | 131 | @Override 132 | public int hashCode() { 133 | return Objects.hash(super.hashCode(), source); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/MpMetricsBinder.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import java.util.Iterator; 4 | 5 | import javax.enterprise.inject.Instance; 6 | import javax.inject.Singleton; 7 | 8 | import io.micrometer.core.instrument.MeterRegistry; 9 | import io.micrometer.core.instrument.binder.MeterBinder; 10 | 11 | @Singleton 12 | class MpMetricsBinder implements MeterBinder { 13 | 14 | final Instance allGaugeAdapters; 15 | 16 | // Micrometer application meter registry 17 | final MetricRegistryAdapter registry; 18 | 19 | MpMetricsBinder(MetricRegistryAdapter registry, 20 | Instance allGaugeAdapters) { 21 | this.registry = registry; 22 | this.allGaugeAdapters = allGaugeAdapters; 23 | } 24 | 25 | @Override 26 | public void bindTo(MeterRegistry r) { 27 | // register all annotation-declared gauges 28 | // this needs to wait until associated/monitored objects can be created. 29 | for (Iterator gauges = allGaugeAdapters.iterator(); gauges.hasNext();) { 30 | registry.bindAnnotatedGauge(gauges.next()); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/MpMetricsRecorder.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.eclipse.microprofile.metrics.MetricRegistry; 7 | 8 | import io.micrometer.core.instrument.MeterRegistry; 9 | import io.quarkus.runtime.RuntimeValue; 10 | import io.quarkus.runtime.annotations.Recorder; 11 | 12 | @Recorder 13 | public class MpMetricsRecorder { 14 | 15 | static Map registries = new HashMap<>(3); 16 | 17 | /* STATIC INIT */ 18 | public void configureRegistryAdapter(RuntimeValue registryRuntimeValue) { 19 | MeterRegistry registry = registryRuntimeValue.getValue(); 20 | 21 | for (MetricRegistry.Type type : MetricRegistry.Type.values()) { 22 | registries.put(type, new MetricRegistryAdapter(type, registry)); 23 | } 24 | } 25 | 26 | static MetricRegistryAdapter getRegistry(MetricRegistry.Type type) { 27 | return registries.get(type); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/MpMetricsRegistryProducer.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.util.*; 5 | 6 | import javax.enterprise.inject.Produces; 7 | import javax.inject.Singleton; 8 | import javax.interceptor.InvocationContext; 9 | 10 | import org.eclipse.microprofile.metrics.*; 11 | import org.eclipse.microprofile.metrics.annotation.RegistryType; 12 | 13 | import io.micrometer.core.instrument.MeterRegistry; 14 | import io.quarkus.arc.ArcInvocationContext; 15 | 16 | @Singleton 17 | @SuppressWarnings("unused") 18 | class MpMetricsRegistryProducer { 19 | @Produces 20 | @Singleton 21 | public MetricRegistryAdapter produceRegistry(MeterRegistry registry) { 22 | return MpMetricsRecorder.getRegistry(MetricRegistry.Type.APPLICATION); 23 | } 24 | 25 | @Produces 26 | @Singleton 27 | @RegistryType(type = MetricRegistry.Type.APPLICATION) 28 | public MetricRegistryAdapter produceApplicationRegistry(MeterRegistry registry) { 29 | return MpMetricsRecorder.getRegistry(MetricRegistry.Type.APPLICATION); 30 | } 31 | 32 | @Produces 33 | @Singleton 34 | @RegistryType(type = MetricRegistry.Type.BASE) 35 | public MetricRegistry produceBaseRegistry(MeterRegistry registry) { 36 | return MpMetricsRecorder.getRegistry(MetricRegistry.Type.BASE); 37 | } 38 | 39 | @Produces 40 | @Singleton 41 | @RegistryType(type = MetricRegistry.Type.VENDOR) 42 | public MetricRegistry produceVendorRegistry(MeterRegistry registry) { 43 | return MpMetricsRecorder.getRegistry(MetricRegistry.Type.VENDOR); 44 | } 45 | 46 | public static T getAnnotation(InvocationContext context, Class annotationClass) { 47 | Set annotations = (Set) context.getContextData() 48 | .get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); 49 | 50 | for (Annotation a : annotations) { 51 | if (annotationClass.isInstance(a)) { 52 | return annotationClass.cast(a); 53 | } 54 | } 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/TimedInterceptor.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import javax.annotation.Priority; 4 | import javax.interceptor.AroundConstruct; 5 | import javax.interceptor.AroundInvoke; 6 | import javax.interceptor.AroundTimeout; 7 | import javax.interceptor.Interceptor; 8 | import javax.interceptor.InvocationContext; 9 | 10 | import org.eclipse.microprofile.metrics.MetricType; 11 | import org.eclipse.microprofile.metrics.annotation.Timed; 12 | 13 | import io.micrometer.core.instrument.Timer; 14 | 15 | @SuppressWarnings("unused") 16 | @Timed 17 | @Interceptor 18 | @Priority(Interceptor.Priority.LIBRARY_BEFORE + 10) 19 | class TimedInterceptor { 20 | 21 | // Micrometer meter registry 22 | final MetricRegistryAdapter mpRegistry; 23 | 24 | TimedInterceptor(MetricRegistryAdapter mpRegistry) { 25 | this.mpRegistry = mpRegistry; 26 | } 27 | 28 | @AroundConstruct 29 | Object timedConstructor(InvocationContext context) throws Exception { 30 | return time(context, context.getConstructor().getDeclaringClass().getSimpleName()); 31 | } 32 | 33 | @AroundInvoke 34 | Object timedMethod(InvocationContext context) throws Exception { 35 | return time(context, context.getMethod().getName()); 36 | } 37 | 38 | @AroundTimeout 39 | Object timedTimeout(InvocationContext context) throws Exception { 40 | return time(context, context.getMethod().getName()); 41 | } 42 | 43 | Object time(InvocationContext context, String methodName) throws Exception { 44 | Timed annotation = MpMetricsRegistryProducer.getAnnotation(context, Timed.class); 45 | if (annotation != null) { 46 | MpMetadata metadata = new MpMetadata(annotation.name().replace("", methodName), 47 | annotation.description().replace("", methodName), 48 | annotation.unit(), 49 | MetricType.TIMER); 50 | TimerAdapter impl = mpRegistry.interceptorTimer(metadata, annotation.tags()); 51 | 52 | Timer.Sample sample = impl.start(); 53 | try { 54 | return context.proceed(); 55 | } finally { 56 | try { 57 | impl.stop(sample); 58 | } catch (Exception e) { 59 | // ignoring on purpose 60 | } 61 | } 62 | } 63 | return context.proceed(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/mpmetrics/TimerAdapter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.mpmetrics; 2 | 3 | import java.time.Duration; 4 | import java.util.concurrent.Callable; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import org.eclipse.microprofile.metrics.MetricType; 8 | import org.eclipse.microprofile.metrics.SimpleTimer; 9 | import org.eclipse.microprofile.metrics.Snapshot; 10 | 11 | import io.micrometer.core.instrument.Meter; 12 | import io.micrometer.core.instrument.MeterRegistry; 13 | import io.micrometer.core.instrument.Timer; 14 | 15 | class TimerAdapter 16 | implements org.eclipse.microprofile.metrics.Timer, org.eclipse.microprofile.metrics.SimpleTimer, MeterHolder { 17 | final MeterRegistry registry; 18 | Timer timer; 19 | 20 | TimerAdapter(MeterRegistry registry) { 21 | this.registry = registry; 22 | } 23 | 24 | public TimerAdapter register(MpMetadata metadata, MetricDescriptor descriptor) { 25 | if (timer == null || metadata.cleanDirtyMetadata()) { 26 | timer = io.micrometer.core.instrument.Timer.builder(descriptor.name()) 27 | .description(metadata.description()) 28 | .tags(descriptor.tags()) 29 | .register(registry); 30 | } 31 | return this; 32 | } 33 | 34 | @Override 35 | public void update(long l, TimeUnit timeUnit) { 36 | timer.record(l, timeUnit); 37 | } 38 | 39 | @Override 40 | public void update(Duration duration) { 41 | timer.record(duration); 42 | } 43 | 44 | @Override 45 | public T time(Callable callable) throws Exception { 46 | return timer.wrap(callable).call(); 47 | } 48 | 49 | @Override 50 | public void time(Runnable runnable) { 51 | timer.wrap(runnable); 52 | } 53 | 54 | @Override 55 | public SampleAdapter time() { 56 | return new SampleAdapter(timer, Timer.start(registry)); 57 | } 58 | 59 | @Override 60 | public Duration getElapsedTime() { 61 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 62 | } 63 | 64 | @Override 65 | public long getCount() { 66 | return timer.count(); 67 | } 68 | 69 | @Override 70 | public double getFifteenMinuteRate() { 71 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 72 | } 73 | 74 | @Override 75 | public double getFiveMinuteRate() { 76 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 77 | } 78 | 79 | @Override 80 | public double getMeanRate() { 81 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 82 | } 83 | 84 | @Override 85 | public double getOneMinuteRate() { 86 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 87 | } 88 | 89 | @Override 90 | public Snapshot getSnapshot() { 91 | throw new UnsupportedOperationException("This operation is not supported when used with micrometer"); 92 | } 93 | 94 | @Override 95 | public Meter getMeter() { 96 | return timer; 97 | } 98 | 99 | public Timer.Sample start() { 100 | return Timer.start(registry); 101 | } 102 | 103 | public void stop(Timer.Sample sample) { 104 | sample.stop(timer); 105 | } 106 | 107 | class SampleAdapter implements org.eclipse.microprofile.metrics.Timer.Context, SimpleTimer.Context { 108 | final Timer timer; 109 | final Timer.Sample sample; 110 | 111 | SampleAdapter(Timer timer, Timer.Sample sample) { 112 | this.sample = sample; 113 | this.timer = timer; 114 | } 115 | 116 | @Override 117 | public long stop() { 118 | return sample.stop(timer); 119 | } 120 | 121 | @Override 122 | public void close() { 123 | sample.stop(timer); 124 | } 125 | } 126 | 127 | @Override 128 | public MetricType getType() { 129 | return MetricType.TIMER; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/vertx/MetricsContext.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.vertx; 2 | 3 | import java.util.HashMap; 4 | 5 | import io.vertx.core.Context; 6 | import io.vertx.ext.web.RoutingContext; 7 | 8 | public class MetricsContext extends HashMap { 9 | static final String METRICS_CONTEXT = "METRICS_CONTEXT"; 10 | 11 | static final String HTTP_REQUEST_PATH = "HTTP_REQUEST_PATH"; 12 | static final String HTTP_REQUEST_SAMPLE = "HTTP_REQUEST_SAMPLE"; 13 | 14 | final Context vertxContext; 15 | volatile RoutingContext routingContext; 16 | 17 | static MetricsContext addMetricsContext(Context vertxContext) { 18 | MetricsContext ctx = new MetricsContext(vertxContext); 19 | vertxContext.put(METRICS_CONTEXT, ctx); 20 | return ctx; 21 | } 22 | 23 | private MetricsContext(Context vertxContext) { 24 | this.vertxContext = vertxContext; 25 | } 26 | 27 | public void removeMetricsContext() { 28 | vertxContext.remove(METRICS_CONTEXT); 29 | } 30 | 31 | T getFromRoutingContext(String key) { 32 | if (routingContext != null) { 33 | return routingContext.get(key); 34 | } 35 | return null; 36 | } 37 | 38 | T getValue(String key) { 39 | Object o = this.get(key); 40 | return o == null ? null : (T) o; 41 | } 42 | 43 | public static void addRoutingContext(Context context, RoutingContext event) { 44 | MetricsContext metricsContext = context.get(METRICS_CONTEXT); 45 | if (metricsContext != null) { 46 | metricsContext.routingContext = event; 47 | } 48 | } 49 | 50 | public static void removeRoutingContext(Context context) { 51 | MetricsContext metricsContext = context.get(METRICS_CONTEXT); 52 | if (metricsContext != null) { 53 | metricsContext.routingContext = null; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.vertx; 2 | 3 | import java.util.concurrent.atomic.AtomicReference; 4 | 5 | import javax.inject.Singleton; 6 | 7 | import org.jboss.logging.Logger; 8 | 9 | import dev.ebullient.micrometer.runtime.config.runtime.VertxConfig; 10 | import io.micrometer.core.instrument.MeterRegistry; 11 | import io.vertx.core.VertxOptions; 12 | import io.vertx.core.http.HttpServerOptions; 13 | import io.vertx.core.metrics.MetricsOptions; 14 | import io.vertx.core.net.SocketAddress; 15 | import io.vertx.core.spi.VertxMetricsFactory; 16 | import io.vertx.core.spi.metrics.HttpServerMetrics; 17 | import io.vertx.core.spi.metrics.VertxMetrics; 18 | 19 | @Singleton 20 | public class VertxMeterBinderAdapter extends MetricsOptions implements VertxMetricsFactory, VertxMetrics { 21 | private static final Logger log = Logger.getLogger(VertxMeterBinderAdapter.class); 22 | 23 | private final static AtomicReference meterRegistryRef = new AtomicReference<>(); 24 | 25 | private VertxConfig config; 26 | 27 | public VertxMeterBinderAdapter() { 28 | } 29 | 30 | public void setVertxConfig(VertxConfig config) { 31 | this.config = config; 32 | } 33 | 34 | public static void setMeterRegistry(MeterRegistry meterRegistry) { 35 | meterRegistryRef.set(meterRegistry); 36 | } 37 | 38 | @Override 39 | public boolean isEnabled() { 40 | return true; 41 | } 42 | 43 | @Override 44 | public VertxMetricsFactory getFactory() { 45 | return this; 46 | } 47 | 48 | @Override 49 | public VertxMetrics metrics(VertxOptions vertxOptions) { 50 | return this; 51 | } 52 | 53 | @Override 54 | public MetricsOptions newOptions() { 55 | return this; 56 | } 57 | 58 | @Override 59 | public HttpServerMetrics createHttpServerMetrics(HttpServerOptions options, SocketAddress localAddress) { 60 | log.debugf("Create HttpServerMetrics with options %s and address %s", options, localAddress); 61 | log.debugf("Bind registry %s to Vertx Metrics", meterRegistryRef.get()); 62 | MeterRegistry registry = meterRegistryRef.get(); 63 | if (registry == null) { 64 | throw new IllegalStateException("MeterRegistry was not resolved"); 65 | } 66 | if (config == null) { 67 | throw new IllegalStateException("VertxConfig was not found"); 68 | } 69 | return new VertxHttpServerMetrics(registry, config); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/vertx/VertxMeterBinderContainerFilter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.vertx; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import javax.enterprise.inject.spi.CDI; 7 | import javax.ws.rs.container.ContainerRequestContext; 8 | import javax.ws.rs.container.ContainerRequestFilter; 9 | import javax.ws.rs.core.MultivaluedMap; 10 | import javax.ws.rs.core.UriInfo; 11 | 12 | import org.jboss.logging.Logger; 13 | 14 | import io.quarkus.vertx.http.runtime.CurrentVertxRequest; 15 | import io.vertx.ext.web.RoutingContext; 16 | 17 | public class VertxMeterBinderContainerFilter implements ContainerRequestFilter { 18 | private static final Logger log = Logger.getLogger(VertxMeterBinderContainerFilter.class); 19 | 20 | @Override 21 | public void filter(final ContainerRequestContext requestContext) { 22 | RoutingContext routingContext = CDI.current().select(CurrentVertxRequest.class).get().getCurrent(); 23 | UriInfo info = requestContext.getUriInfo(); 24 | 25 | MultivaluedMap pathParameters = info.getPathParameters(); 26 | if (!pathParameters.isEmpty() && routingContext != null) { 27 | // Replace parameter values in the URI with {key}: /items/123 -> /items/{id} 28 | String path = info.getPath(); 29 | for (Map.Entry> entry : pathParameters.entrySet()) { 30 | for (String value : entry.getValue()) { 31 | path = path.replace(value, "{" + entry.getKey() + "}"); 32 | } 33 | } 34 | 35 | log.debugf("Saving parameterized path %s in %s", path, routingContext); 36 | routingContext.put(MetricsContext.HTTP_REQUEST_PATH, path); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/vertx/VertxMeterBinderRecorder.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.vertx; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.jboss.logging.Logger; 6 | 7 | import dev.ebullient.micrometer.runtime.config.runtime.VertxConfig; 8 | import io.quarkus.arc.Arc; 9 | import io.quarkus.runtime.annotations.Recorder; 10 | import io.vertx.core.VertxOptions; 11 | 12 | @Recorder 13 | public class VertxMeterBinderRecorder { 14 | private static final Logger log = Logger.getLogger(VertxMeterBinderRecorder.class); 15 | 16 | /* STATIC_INIT */ 17 | public Consumer configureMetricsAdapter() { 18 | return new Consumer() { 19 | @Override 20 | public void accept(VertxOptions vertxOptions) { 21 | log.debug("Adding Micrometer MeterBinder to VertxOptions"); 22 | VertxMeterBinderAdapter binder = Arc.container().instance(VertxMeterBinderAdapter.class).get(); 23 | vertxOptions.setMetricsOptions(binder); 24 | } 25 | }; 26 | } 27 | 28 | /* RUNTIME_INIT */ 29 | public void setVertxConfig(VertxConfig config) { 30 | VertxMeterBinderAdapter binder = Arc.container().instance(VertxMeterBinderAdapter.class).get(); 31 | binder.setVertxConfig(config); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/vertx/VertxMeterFilter.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.vertx; 2 | 3 | import org.jboss.logging.Logger; 4 | 5 | import io.vertx.core.Context; 6 | import io.vertx.core.Handler; 7 | import io.vertx.core.Vertx; 8 | import io.vertx.ext.web.RoutingContext; 9 | 10 | public class VertxMeterFilter implements Handler { 11 | private static final Logger log = Logger.getLogger(VertxMeterFilter.class); 12 | 13 | @Override 14 | public void handle(RoutingContext event) { 15 | final Context context = Vertx.currentContext(); 16 | log.debugf("Handling event %s with context %s", event, context); 17 | 18 | MetricsContext.addRoutingContext(context, event); 19 | event.addBodyEndHandler(new Handler() { 20 | @Override 21 | public void handle(Void x) { 22 | MetricsContext.removeRoutingContext(context); 23 | } 24 | }); 25 | 26 | event.next(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/vertx/VertxNetworkMetrics.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.vertx; 2 | 3 | import io.micrometer.core.instrument.DistributionSummary; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import io.vertx.core.net.SocketAddress; 6 | import io.vertx.core.spi.metrics.NetworkMetrics; 7 | 8 | /** 9 | * NetworkMetrics 10 | *
    11 | *
  • S for Socket metric -- Vert.x Context
  • 12 | *
13 | */ 14 | public class VertxNetworkMetrics implements NetworkMetrics { 15 | final MeterRegistry registry; 16 | 17 | final String nameBytesRead; 18 | final String nameBytesWritten; 19 | final String nameExceptionOccurred; 20 | 21 | VertxNetworkMetrics(MeterRegistry registry, String prefix) { 22 | this.registry = registry; 23 | nameBytesRead = prefix + ".bytes.read"; 24 | nameBytesWritten = prefix + ".bytes.written"; 25 | nameExceptionOccurred = prefix + ".errors"; 26 | } 27 | 28 | /** 29 | * Called when bytes have been read 30 | * 31 | * @param socketMetric the socket metric, null for UDP 32 | * @param remoteAddress the remote address which this socket received bytes from 33 | * @param numberOfBytes the number of bytes read 34 | */ 35 | @Override 36 | public void bytesRead(MetricsContext socketMetric, SocketAddress remoteAddress, long numberOfBytes) { 37 | DistributionSummary.builder(nameBytesRead).register(registry).record(numberOfBytes); 38 | } 39 | 40 | /** 41 | * Called when bytes have been written 42 | * 43 | * @param socketMetric the socket metric, null for UDP 44 | * @param remoteAddress the remote address which bytes are being written to 45 | * @param numberOfBytes the number of bytes written 46 | */ 47 | @Override 48 | public void bytesWritten(MetricsContext socketMetric, SocketAddress remoteAddress, long numberOfBytes) { 49 | DistributionSummary.builder(nameBytesWritten).register(registry).record(numberOfBytes); 50 | } 51 | 52 | /** 53 | * Called when exceptions occur for a specific connection. 54 | * 55 | * @param socketMetric the socket metric, null for UDP 56 | * @param remoteAddress the remote address of the connection or null if it's 57 | * datagram/udp 58 | * @param t the exception that occurred 59 | */ 60 | @Override 61 | public void exceptionOccurred(MetricsContext socketMetric, SocketAddress remoteAddress, Throwable t) { 62 | registry.counter(nameExceptionOccurred, "class", t.getClass().getName()).increment(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/binder/vertx/VertxTcpMetrics.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.vertx; 2 | 3 | import io.micrometer.core.instrument.LongTaskTimer; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import io.vertx.core.Context; 6 | import io.vertx.core.Vertx; 7 | import io.vertx.core.net.SocketAddress; 8 | import io.vertx.core.spi.metrics.TCPMetrics; 9 | 10 | public class VertxTcpMetrics extends VertxNetworkMetrics 11 | implements TCPMetrics { 12 | private static final String CONNECTED_SOCKET_SAMPLE = "CONNECTED_SOCKET_SAMPLE"; 13 | 14 | final String nameConnections; 15 | 16 | VertxTcpMetrics(MeterRegistry registry, String prefix) { 17 | super(registry, prefix); 18 | nameConnections = prefix + ".connections"; 19 | } 20 | 21 | /** 22 | * Called when a client has connected, which is applicable for TCP connections. 23 | *

24 | * The remote name of the client is a best effort to provide the name of the 25 | * remote host, i.e if the name is specified at creation time, this name will be 26 | * used otherwise it will be the remote address. 27 | * 28 | * @param remoteAddress the remote address of the client 29 | * @param remoteName the remote name of the client 30 | * @return the socket metric 31 | */ 32 | @Override 33 | public MetricsContext connected(SocketAddress remoteAddress, String remoteName) { 34 | Context vertxContext = Vertx.currentContext(); 35 | MetricsContext metricsContext = MetricsContext.addMetricsContext(vertxContext); 36 | 37 | metricsContext.put(CONNECTED_SOCKET_SAMPLE, 38 | LongTaskTimer.builder(nameConnections).register(registry).start()); 39 | return metricsContext; 40 | } 41 | 42 | /** 43 | * Called when a client has disconnected, which is applicable for TCP 44 | * connections. 45 | * 46 | * @param socketMetric the socket metric 47 | * @param remoteAddress the remote address of the client 48 | */ 49 | @Override 50 | public void disconnected(MetricsContext socketMetric, SocketAddress remoteAddress) { 51 | if (socketMetric != null) { 52 | LongTaskTimer.Sample sample = (LongTaskTimer.Sample) socketMetric.get(CONNECTED_SOCKET_SAMPLE); 53 | if (sample != null) { 54 | sample.stop(); 55 | } 56 | socketMetric.removeMetricsContext(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/DatadogConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config; 2 | 3 | import java.util.Optional; 4 | 5 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig.CapabilityEnabled; 6 | import io.quarkus.runtime.annotations.ConfigGroup; 7 | import io.quarkus.runtime.annotations.ConfigItem; 8 | 9 | @ConfigGroup 10 | public class DatadogConfig implements CapabilityEnabled { 11 | /** 12 | * Support for export to Datadog 13 | *

14 | * Support for Datadog will be enabled if micrometer 15 | * support is enabled, the DatadogMeterRegistry is on the classpath 16 | * and either this value is true, or this value is unset and 17 | * {@code quarkus.micrometer.registry-enabled-default} is true. 18 | */ 19 | @ConfigItem 20 | public Optional enabled; 21 | 22 | @Override 23 | public Optional getEnabled() { 24 | return enabled; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return this.getClass().getSimpleName() 30 | + "{enabled=" + enabled 31 | + '}'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/JmxConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config; 2 | 3 | import java.util.Optional; 4 | 5 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig.CapabilityEnabled; 6 | import io.quarkus.runtime.annotations.ConfigGroup; 7 | import io.quarkus.runtime.annotations.ConfigItem; 8 | 9 | @ConfigGroup 10 | public class JmxConfig implements CapabilityEnabled { 11 | /** 12 | * Support for export to JMX 13 | *

14 | * Support for JMX will be enabled if micrometer 15 | * support is enabled, the JmxMeterRegistry is on the classpath 16 | * and either this value is true, or this value is unset and 17 | * {@code quarkus.micrometer.registry-enabled-default} is true. 18 | */ 19 | @ConfigItem 20 | public Optional enabled; 21 | 22 | @Override 23 | public Optional getEnabled() { 24 | return enabled; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return this.getClass().getSimpleName() 30 | + "{enabled=" + enabled 31 | + '}'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/MicrometerConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config; 2 | 3 | import java.util.Optional; 4 | 5 | import io.quarkus.runtime.annotations.ConfigGroup; 6 | import io.quarkus.runtime.annotations.ConfigItem; 7 | import io.quarkus.runtime.annotations.ConfigPhase; 8 | import io.quarkus.runtime.annotations.ConfigRoot; 9 | 10 | /** 11 | * Global configuration for the Micrometer extension 12 | */ 13 | @ConfigRoot(name = "micrometer", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) 14 | public final class MicrometerConfig { 15 | 16 | /** 17 | * Micrometer metrics support. 18 | *

19 | * Micrometer metrics support is enabled by default. 20 | */ 21 | @ConfigItem(defaultValue = "true") 22 | public boolean enabled; 23 | 24 | /** 25 | * Micrometer MeterRegistry discovery. 26 | *

27 | * Micrometer MeterRegistry implementations discovered on the classpath 28 | * will be enabled automatically by default. 29 | */ 30 | @ConfigItem(defaultValue = "true") 31 | public boolean registryEnabledDefault; 32 | 33 | /** 34 | * Micrometer MeterBinder discovery. 35 | *

36 | * Micrometer MeterBinder implementations discovered on the classpath 37 | * will be enabled automatically by default. 38 | */ 39 | @ConfigItem(defaultValue = "true") 40 | public boolean binderEnabledDefault; 41 | 42 | /** Build / static runtime config for binders */ 43 | public BinderConfig binder; 44 | 45 | /** Build / static runtime config for exporters */ 46 | public ExportConfig export; 47 | 48 | /** 49 | * For MeterRegistry configurations with optional 'enabled' attributes, 50 | * determine whether or not the registry is enabled using {@link #registryEnabledDefault} 51 | * as the default value. 52 | */ 53 | public boolean checkRegistryEnabledWithDefault(CapabilityEnabled config) { 54 | if (enabled) { 55 | Optional configValue = config.getEnabled(); 56 | if (configValue.isPresent()) { 57 | return configValue.get(); 58 | } else { 59 | return registryEnabledDefault; 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | /** 66 | * For MeterBinder configurations with optional 'enabled' attributes, 67 | * determine whether or not the binder is enabled using {@link #binderEnabledDefault} 68 | * as the default value. 69 | */ 70 | public boolean checkBinderEnabledWithDefault(CapabilityEnabled config) { 71 | if (enabled) { 72 | Optional configValue = config.getEnabled(); 73 | if (configValue.isPresent()) { 74 | return configValue.get(); 75 | } else { 76 | return binderEnabledDefault; 77 | } 78 | } 79 | return false; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return this.getClass().getSimpleName() 85 | + "{enabled=" + enabled 86 | + ",binderEnabledDefault=" + binderEnabledDefault 87 | + ",registryEnabledDefault=" + registryEnabledDefault 88 | + '}'; 89 | } 90 | 91 | /** Build / static runtime config for binders */ 92 | @ConfigGroup 93 | public static class BinderConfig { 94 | public VertxConfig vertx; 95 | public MicroprofileMetricsConfig mpMetrics; 96 | 97 | /** 98 | * Micrometer JVM metrics support. 99 | *

100 | * Micrometer JVM metrics support is enabled by default. 101 | */ 102 | @ConfigItem(defaultValue = "true") 103 | public boolean jvm; 104 | 105 | /** 106 | * Micrometer System metrics support. 107 | *

108 | * Micrometer System metrics support is enabled by default. 109 | */ 110 | @ConfigItem(defaultValue = "true") 111 | public boolean system; 112 | } 113 | 114 | /** Build / static runtime config for exporters */ 115 | @ConfigGroup 116 | public static class ExportConfig { 117 | public DatadogConfig datadog; 118 | public JmxConfig jmx; 119 | public PrometheusConfig prometheus; 120 | public StackdriverConfig stackdriver; 121 | } 122 | 123 | public static interface CapabilityEnabled { 124 | Optional getEnabled(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/MicroprofileMetricsConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config; 2 | 3 | import java.util.Optional; 4 | 5 | import io.quarkus.runtime.annotations.ConfigGroup; 6 | import io.quarkus.runtime.annotations.ConfigItem; 7 | 8 | /** 9 | * Build / static runtime config for the Microprofile Metrics Binder 10 | */ 11 | @ConfigGroup 12 | public class MicroprofileMetricsConfig implements MicrometerConfig.CapabilityEnabled { 13 | /** 14 | * Microprofile Metrics support. 15 | *

16 | * Support for Microprofile metrics will be enabled if micrometer 17 | * support is enabled, and this value is true. You need to also 18 | * include the microprofile api jar to your dependencies: 19 | * 20 | *

21 |      * <dependency>
22 |      *   <groupId>org.eclipse.microprofile.metrics</groupId>
23 |      *   <artifactId>microprofile-metrics-api</artifactId>
24 |      * </dependency>
25 |      * 
26 | */ 27 | @ConfigItem 28 | public Optional enabled; 29 | 30 | @Override 31 | public Optional getEnabled() { 32 | return enabled; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return this.getClass().getSimpleName() 38 | + "{enabled=" + enabled 39 | + '}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/PrometheusConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config; 2 | 3 | import java.util.Optional; 4 | 5 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig.CapabilityEnabled; 6 | import io.quarkus.runtime.annotations.ConfigGroup; 7 | import io.quarkus.runtime.annotations.ConfigItem; 8 | 9 | @ConfigGroup 10 | public class PrometheusConfig implements CapabilityEnabled { 11 | /** 12 | * Support for export to Prometheus. 13 | *

14 | * Support for Prometheus will be enabled if micrometer 15 | * support is enabled, the PrometheusMeterRegistry is on the classpath 16 | * and either this value is true, or this value is unset and 17 | * {@code quarkus.micrometer.registry-enabled-default} is true. 18 | */ 19 | @ConfigItem 20 | public Optional enabled; 21 | 22 | /** 23 | * The path for the prometheus endpoint. The default value is {@code /prometheus}. 24 | */ 25 | @ConfigItem(defaultValue = "/prometheus") 26 | public String path; 27 | 28 | @Override 29 | public Optional getEnabled() { 30 | return enabled; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return this.getClass().getSimpleName() 36 | + "{path='" + path 37 | + ",enabled=" + enabled 38 | + '}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/StackdriverConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config; 2 | 3 | import java.util.Optional; 4 | 5 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig.CapabilityEnabled; 6 | import io.quarkus.runtime.annotations.ConfigGroup; 7 | import io.quarkus.runtime.annotations.ConfigItem; 8 | 9 | @ConfigGroup 10 | public class StackdriverConfig implements CapabilityEnabled { 11 | /** 12 | * Support for export to Stackdriver. 13 | *

14 | * Support for Stackdriver will be enabled if micrometer 15 | * support is enabled, the StackdriverMeterRegistry is on the classpath 16 | * and either this value is true, or this value is unset and 17 | * {@code quarkus.micrometer.registry-enabled-default} is true. 18 | * 19 | * [NOTE] 20 | * ==== 21 | * Stackdriver libraries do not yet support running in native mode. 22 | * The Stackdriver MeterRegistry will be automatically disabled 23 | * for native builds. 24 | * 25 | * See https://github.com/grpc/grpc-java/issues/5460 26 | * ==== 27 | * 28 | * @asciidoclet 29 | */ 30 | @ConfigItem 31 | public Optional enabled; 32 | 33 | @Override 34 | public Optional getEnabled() { 35 | return enabled; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return this.getClass().getSimpleName() 41 | + "{enabled=" + enabled 42 | + '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/VertxConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config; 2 | 3 | import java.util.Optional; 4 | 5 | import dev.ebullient.micrometer.runtime.config.MicrometerConfig.CapabilityEnabled; 6 | import io.quarkus.runtime.annotations.ConfigGroup; 7 | import io.quarkus.runtime.annotations.ConfigItem; 8 | 9 | /** 10 | * Build / static runtime config for the Vert.x Binder 11 | */ 12 | @ConfigGroup 13 | public class VertxConfig implements CapabilityEnabled { 14 | /** 15 | * Vert.x metrics support. 16 | *

17 | * Support for Vert.x metrics will be enabled if micrometer 18 | * support is enabled, Vert.x MetricsOptions is on the classpath 19 | * and either this value is true, or this value is unset and 20 | * {@code quarkus.micrometer.binder-enabled-default} is true. 21 | */ 22 | @ConfigItem 23 | public Optional enabled; 24 | 25 | @Override 26 | public Optional getEnabled() { 27 | return enabled; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return this.getClass().getSimpleName() 33 | + "{enabled=" + enabled 34 | + '}'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/runtime/ExportConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config.runtime; 2 | 3 | import java.util.Map; 4 | 5 | import io.quarkus.runtime.annotations.ConfigItem; 6 | import io.quarkus.runtime.annotations.ConfigPhase; 7 | import io.quarkus.runtime.annotations.ConfigRoot; 8 | 9 | /** 10 | * Runtime configuration for Micrometer meter registries. 11 | *

12 | * 13 | * [NOTE] 14 | * ==== 15 | * Not all the dialects are supported in GraalVM native executables: we currently provide driver extensions for PostgreSQL, 16 | * MariaDB, Microsoft SQL Server and H2. 17 | * ==== 18 | * 19 | * @asciidoclet 20 | */ 21 | @ConfigRoot(name = "micrometer.export", phase = ConfigPhase.RUN_TIME) 22 | public class ExportConfig { 23 | 24 | // @formatter:off 25 | /** 26 | * Datadog MeterRegistry configuration 27 | *

28 | * A property source for configuration of the Datadog MeterRegistry to push 29 | * metrics using the Datadog API, see https://micrometer.io/docs/registry/datadog. 30 | * 31 | * [cols="1,2", options="header"] 32 | * .Properties 33 | * |=== 34 | * | Property=Default 35 | * | Description 36 | * 37 | * | `apiKey=YOUR_KEY` 38 | * | Define the key used to push data using the Datadog API 39 | * 40 | * | `publish=true` 41 | * | By default, gathered metrics will be published to Datadog when the MeterRegistry is enabled. 42 | * Use this attribute to selectively disable publication of metrics in some environments. 43 | * 44 | * | `step=1m` 45 | * | The interval at which metrics are sent to Datadog. The default is 1 minute. 46 | * |=== 47 | * 48 | * Other micrometer configuration attributes can also be specified. 49 | * 50 | * @asciidoclet 51 | */ 52 | // @formatter:on 53 | @ConfigItem 54 | Map datadog; 55 | 56 | // @formatter:off 57 | /** 58 | * JMX registry configuration properties. 59 | *

60 | * A property source for configuration of the JMX MeterRegistry, 61 | * see https://micrometer.io/docs/registry/jmx. 62 | * 63 | * @asciidoclet 64 | */ 65 | // @formatter:on 66 | @ConfigItem 67 | Map jmx; 68 | 69 | // @formatter:off 70 | /** 71 | * Prometheus registry configuration properties. 72 | *

73 | * A property source for configuration of the Prometheus MeterRegistry, 74 | * see https://micrometer.io/docs/registry/prometheus 75 | * 76 | * @asciidoclet 77 | */ 78 | // @formatter:on 79 | @ConfigItem 80 | Map prometheus; 81 | 82 | // @formatter:off 83 | /** 84 | * Stackdriver registry configuration properties. 85 | *

86 | * A property source for configuration of the Stackdriver MeterRegistry, 87 | * see https://micrometer.io/docs/registry/stackdriver. 88 | * 89 | * [cols="1,2", options="header"] 90 | * .Properties 91 | * |=== 92 | * | Property=Default 93 | * | Description 94 | * 95 | * | `project-id=MY_PROJECT_ID` 96 | * | Define the project id used to push data to Stackdriver Monitoring 97 | * 98 | * | `publish=true` 99 | * | By default, gathered metrics will be published to Datadog when the MeterRegistry is enabled. 100 | * Use this attribute to selectively disable publication of metrics in some environments. 101 | * 102 | * | `step=1m` 103 | * | The interval at which metrics are sent to Stackdriver Monitoring. The default is 1 minute. 104 | * |=== 105 | * 106 | * Other micrometer configuration attributes can also be specified. 107 | * 108 | * @asciidoclet 109 | */ 110 | // @formatter:on 111 | @ConfigItem 112 | Map stackdriver; 113 | } 114 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/config/runtime/VertxConfig.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.config.runtime; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import io.quarkus.runtime.annotations.ConfigItem; 7 | import io.quarkus.runtime.annotations.ConfigPhase; 8 | import io.quarkus.runtime.annotations.ConfigRoot; 9 | 10 | @ConfigRoot(name = "micrometer.binder.vertx", phase = ConfigPhase.RUN_TIME) 11 | public class VertxConfig { 12 | /** 13 | * Comma-separated case-sensitive list of regular expressions defining Paths 14 | * that should be matched and used as tags. By default, the first path 15 | * segment will be used. This default behavior will also apply to any 16 | * URI path that is found (2xx or 5xx) but doesn't match elements 17 | * in this list. 18 | */ 19 | @ConfigItem 20 | public Optional> matchPatterns = Optional.empty(); 21 | 22 | /** 23 | * Comma-separated case-sensitive list of regular expressions defining Paths 24 | * that should be ignored / not measured. 25 | */ 26 | @ConfigItem 27 | public Optional> ignorePatterns = Optional.empty(); 28 | } 29 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/export/DatadogMeterRegistryProvider.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.export; 2 | 3 | import java.util.Map; 4 | 5 | import javax.enterprise.inject.Produces; 6 | import javax.inject.Singleton; 7 | 8 | import org.eclipse.microprofile.config.Config; 9 | 10 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 11 | import io.micrometer.core.instrument.Clock; 12 | import io.micrometer.datadog.DatadogConfig; 13 | import io.micrometer.datadog.DatadogMeterRegistry; 14 | import io.quarkus.arc.DefaultBean; 15 | 16 | @Singleton 17 | public class DatadogMeterRegistryProvider { 18 | static final String PREFIX = "quarkus.micrometer.export.datadog."; 19 | static final String PUBLISH = "datadog.publish"; 20 | static final String ENABLED = "datadog.enabled"; 21 | 22 | @Produces 23 | @Singleton 24 | @DefaultBean 25 | public DatadogConfig configure(Config config) { 26 | final Map properties = MicrometerRecorder.captureProperties(config, PREFIX); 27 | 28 | // Special check: if publish is set, override the value of enabled 29 | // Specifically, The datadog registry must be enabled for this 30 | // Provider to even be present. If this instance (at runtime) wants 31 | // to prevent metrics from being published, then it would set 32 | // quarkus.micrometer.export.datadog.publish=false 33 | if (properties.containsKey(PUBLISH)) { 34 | properties.put(ENABLED, properties.get(PUBLISH)); 35 | } 36 | 37 | return new DatadogConfig() { 38 | @Override 39 | public String get(String key) { 40 | return properties.get(key); 41 | } 42 | }; 43 | } 44 | 45 | @Produces 46 | @Singleton 47 | public DatadogMeterRegistry registry(DatadogConfig config, Clock clock) { 48 | return DatadogMeterRegistry.builder(config) 49 | .clock(clock) 50 | .build(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/export/JmxMeterRegistryProvider.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.export; 2 | 3 | import java.util.Map; 4 | 5 | import javax.enterprise.inject.Produces; 6 | import javax.inject.Singleton; 7 | 8 | import org.eclipse.microprofile.config.Config; 9 | 10 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 11 | import io.micrometer.core.instrument.Clock; 12 | import io.micrometer.core.instrument.util.HierarchicalNameMapper; 13 | import io.micrometer.jmx.JmxConfig; 14 | import io.micrometer.jmx.JmxMeterRegistry; 15 | import io.quarkus.arc.DefaultBean; 16 | 17 | @Singleton 18 | public class JmxMeterRegistryProvider { 19 | static final String PREFIX = "quarkus.micrometer.export.jmx."; 20 | 21 | @Produces 22 | @Singleton 23 | @DefaultBean 24 | public HierarchicalNameMapper config() { 25 | return HierarchicalNameMapper.DEFAULT; 26 | } 27 | 28 | @Produces 29 | @Singleton 30 | @DefaultBean 31 | public JmxConfig configure(Config config) { 32 | final Map properties = MicrometerRecorder.captureProperties(config, PREFIX); 33 | 34 | return new JmxConfig() { 35 | @Override 36 | public String get(String key) { 37 | return properties.get(key); 38 | } 39 | }; 40 | } 41 | 42 | @Produces 43 | @Singleton 44 | public JmxMeterRegistry registry(JmxConfig config, Clock clock, HierarchicalNameMapper nameMapper) { 45 | return new JmxMeterRegistry(config, clock, nameMapper); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/export/PrometheusMeterRegistryProvider.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.export; 2 | 3 | import java.util.Map; 4 | 5 | import javax.enterprise.inject.Produces; 6 | import javax.inject.Singleton; 7 | 8 | import org.eclipse.microprofile.config.Config; 9 | 10 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 11 | import io.micrometer.core.instrument.Clock; 12 | import io.micrometer.prometheus.PrometheusConfig; 13 | import io.micrometer.prometheus.PrometheusMeterRegistry; 14 | import io.prometheus.client.CollectorRegistry; 15 | import io.quarkus.arc.DefaultBean; 16 | 17 | @Singleton 18 | public class PrometheusMeterRegistryProvider { 19 | static final String PREFIX = "quarkus.micrometer.export.prometheus."; 20 | 21 | @Produces 22 | @Singleton 23 | @DefaultBean 24 | public PrometheusConfig configure(Config config) { 25 | final Map properties = MicrometerRecorder.captureProperties(config, PREFIX); 26 | 27 | return new PrometheusConfig() { 28 | @Override 29 | public String get(String key) { 30 | return properties.get(key); 31 | } 32 | }; 33 | } 34 | 35 | @Produces 36 | @DefaultBean 37 | public CollectorRegistry collectorRegistry() { 38 | return new CollectorRegistry(true); 39 | } 40 | 41 | @Produces 42 | @Singleton 43 | // Quarkus #8895 : @AlternativePriority(Interceptor.Priority.APPLICATION + 100) 44 | public PrometheusMeterRegistry registry(PrometheusConfig config, CollectorRegistry collectorRegistry, Clock clock) { 45 | return new PrometheusMeterRegistry(config, collectorRegistry, clock); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/export/PrometheusRecorder.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.export; 2 | 3 | import java.util.function.Function; 4 | 5 | import dev.ebullient.micrometer.runtime.export.handlers.PrometheusHandler; 6 | import io.prometheus.client.exporter.common.TextFormat; 7 | import io.quarkus.runtime.annotations.Recorder; 8 | import io.vertx.ext.web.Route; 9 | import io.vertx.ext.web.Router; 10 | 11 | @Recorder 12 | public class PrometheusRecorder { 13 | PrometheusHandler handler; 14 | 15 | public PrometheusHandler getHandler() { 16 | if (handler == null) { 17 | handler = new PrometheusHandler(); 18 | } 19 | 20 | return handler; 21 | } 22 | 23 | public Function route(String path) { 24 | return new Function() { 25 | @Override 26 | public Route apply(Router router) { 27 | Route route = router.route(path) 28 | .produces(TextFormat.CONTENT_TYPE_004); 29 | return route; 30 | } 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/export/StackdriverMeterRegistryProvider.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.export; 2 | 3 | import java.util.Map; 4 | 5 | import javax.enterprise.inject.Produces; 6 | import javax.inject.Singleton; 7 | 8 | import org.eclipse.microprofile.config.Config; 9 | 10 | import dev.ebullient.micrometer.runtime.MicrometerRecorder; 11 | import io.micrometer.core.instrument.Clock; 12 | import io.micrometer.stackdriver.StackdriverConfig; 13 | import io.micrometer.stackdriver.StackdriverMeterRegistry; 14 | import io.quarkus.arc.DefaultBean; 15 | 16 | @Singleton 17 | public class StackdriverMeterRegistryProvider { 18 | static final String PREFIX = "quarkus.micrometer.export.stackdriver."; 19 | static final String PUBLISH = "stackdriver.publish"; 20 | static final String ENABLED = "stackdriver.enabled"; 21 | 22 | @Produces 23 | @Singleton 24 | @DefaultBean 25 | public StackdriverConfig configure(Config config) { 26 | final Map properties = MicrometerRecorder.captureProperties(config, PREFIX); 27 | 28 | // Special check: if publish is set, override the value of enabled 29 | // Specifically, the stackdriver registry must be enabled for this 30 | // Provider to even be present. If this instance (at runtime) wants 31 | // to prevent metrics from being published, then it would set 32 | // quarkus.micrometer.export.stackdriver.publish=false 33 | if (properties.containsKey(PUBLISH)) { 34 | properties.put(ENABLED, properties.get(PUBLISH)); 35 | } 36 | 37 | return new StackdriverConfig() { 38 | @Override 39 | public String get(String key) { 40 | return properties.get(key); 41 | } 42 | }; 43 | } 44 | 45 | @Produces 46 | @Singleton 47 | public StackdriverMeterRegistry registry(StackdriverConfig config, Clock clock) { 48 | return new StackdriverMeterRegistry(config, clock); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /runtime/src/main/java/dev/ebullient/micrometer/runtime/export/handlers/PrometheusHandler.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.export.handlers; 2 | 3 | import javax.enterprise.inject.Default; 4 | import javax.enterprise.inject.Instance; 5 | import javax.enterprise.inject.spi.CDI; 6 | 7 | import org.jboss.logging.Logger; 8 | 9 | import io.micrometer.prometheus.PrometheusMeterRegistry; 10 | import io.prometheus.client.exporter.common.TextFormat; 11 | import io.vertx.core.Handler; 12 | import io.vertx.core.buffer.Buffer; 13 | import io.vertx.core.http.HttpServerResponse; 14 | import io.vertx.ext.web.RoutingContext; 15 | 16 | public class PrometheusHandler implements Handler { 17 | private static final Logger log = Logger.getLogger(PrometheusHandler.class); 18 | 19 | private PrometheusMeterRegistry registry; 20 | 21 | private boolean setup = false; 22 | 23 | @Override 24 | public void handle(RoutingContext routingContext) { 25 | if (!setup) { 26 | setup(); 27 | } 28 | 29 | HttpServerResponse response = routingContext.response(); 30 | if (registry == null) { 31 | response.setStatusCode(500) 32 | .setStatusMessage("Unable to resolve Prometheus registry instance"); 33 | } else { 34 | response.putHeader("Content-Type", TextFormat.CONTENT_TYPE_004) 35 | .end(Buffer.buffer(registry.scrape())); 36 | } 37 | } 38 | 39 | private void setup() { 40 | Instance registries = CDI.current().select(PrometheusMeterRegistry.class, 41 | Default.Literal.INSTANCE); 42 | 43 | if (registries.isUnsatisfied()) { 44 | registry = null; 45 | } else if (registries.isAmbiguous()) { 46 | registry = registries.iterator().next(); 47 | log.warnf("Multiple prometheus registries present: %s. Using %s with the built in scrape endpoint", registries, 48 | registry); 49 | } else { 50 | registry = registries.get(); 51 | } 52 | 53 | setup = true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /runtime/src/main/resources/META-INF/quarkus-extension.yaml: -------------------------------------------------------------------------------- 1 | name: "Micrometer - SLF4J for metrics" 2 | metadata: 3 | keywords: 4 | - "micrometer" 5 | - "metrics" 6 | - "metric" 7 | - "prometheus" 8 | guide: "https://github.com/ebullient/quarkus-micrometer-extension" 9 | categories: 10 | - "observability" 11 | status: "experimental" 12 | -------------------------------------------------------------------------------- /runtime/src/test/java/dev.ebullient.micrometer.runtime.binder.vertx/VertxMetricsTagsTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder.vertx; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.regex.Pattern; 7 | 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.condition.DisabledOnJre; 12 | import org.junit.jupiter.api.condition.JRE; 13 | import org.mockito.Mock; 14 | import org.mockito.MockitoAnnotations; 15 | 16 | import io.micrometer.core.instrument.Tag; 17 | import io.vertx.core.http.HttpServerResponse; 18 | 19 | /** 20 | * Test tag creation 21 | */ 22 | @DisabledOnJre(JRE.JAVA_8) 23 | public class VertxMetricsTagsTest { 24 | 25 | @Mock 26 | HttpServerResponse response; 27 | 28 | final List NO_IGNORE_PATTERNS = Collections.emptyList(); 29 | final List NO_MATCH_PATTERNS = Collections.emptyList(); 30 | 31 | @BeforeEach 32 | public void initMocks() { 33 | MockitoAnnotations.initMocks(this); 34 | } 35 | 36 | @Test 37 | public void testParsePathNoIgnorePatterns() { 38 | Assertions.assertEquals("/", VertxMetricsTags.parseUriPath(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "//")); 39 | Assertions.assertEquals("/", VertxMetricsTags.parseUriPath(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "")); 40 | Assertions.assertEquals("/path/with/no/leading/slash", 41 | VertxMetricsTags.parseUriPath(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "path/with/no/leading/slash")); 42 | Assertions.assertEquals("/path/with/query/string", 43 | VertxMetricsTags.parseUriPath(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "/path/with/query/string?stuff")); 44 | } 45 | 46 | @Test 47 | public void testParsePathWithIgnorePatterns() { 48 | List ignorePatterns = Arrays.asList(Pattern.compile("/ignore.*")); 49 | 50 | Assertions.assertNull( 51 | VertxMetricsTags.parseUriPath(NO_MATCH_PATTERNS, ignorePatterns, "ignore/me/with/no/leading/slash")); 52 | Assertions.assertNull( 53 | VertxMetricsTags.parseUriPath(NO_MATCH_PATTERNS, ignorePatterns, "/ignore/me/with/query/string?stuff")); 54 | } 55 | 56 | @Test 57 | public void testStatus() { 58 | Assertions.assertEquals(Tag.of("status", "200"), VertxMetricsTags.status(200)); 59 | Assertions.assertEquals(Tag.of("status", "301"), VertxMetricsTags.status(301)); 60 | Assertions.assertEquals(Tag.of("status", "304"), VertxMetricsTags.status(304)); 61 | Assertions.assertEquals(Tag.of("status", "404"), VertxMetricsTags.status(404)); 62 | } 63 | 64 | @Test 65 | public void testUriRedirect() { 66 | Assertions.assertEquals(VertxMetricsTags.URI_REDIRECTION, VertxMetricsTags.uri("/moved", 301)); 67 | Assertions.assertEquals(VertxMetricsTags.URI_REDIRECTION, VertxMetricsTags.uri("/moved", 302)); 68 | Assertions.assertEquals(VertxMetricsTags.URI_REDIRECTION, VertxMetricsTags.uri("/moved", 304)); 69 | } 70 | 71 | @Test 72 | public void testUriDefaults() { 73 | Assertions.assertEquals(VertxMetricsTags.URI_ROOT, VertxMetricsTags.uri("/", 200)); 74 | Assertions.assertEquals(Tag.of("uri", "/known/ok"), VertxMetricsTags.uri("/known/ok", 200)); 75 | Assertions.assertEquals(VertxMetricsTags.URI_NOT_FOUND, VertxMetricsTags.uri("/invalid", 404)); 76 | Assertions.assertEquals(Tag.of("uri", "/known/bad/request"), VertxMetricsTags.uri("/known/bad/request", 400)); 77 | Assertions.assertEquals(Tag.of("uri", "/known/server/error"), VertxMetricsTags.uri("/known/server/error", 500)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /runtime/src/test/java/dev/ebullient/micrometer/runtime/binder/JvmMetricsInfoTest.java: -------------------------------------------------------------------------------- 1 | package dev.ebullient.micrometer.runtime.binder; 2 | 3 | import java.util.Collection; 4 | 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import io.micrometer.core.instrument.Gauge; 9 | import io.micrometer.core.instrument.Meter; 10 | import io.micrometer.core.instrument.MeterRegistry; 11 | import io.micrometer.core.instrument.search.Search; 12 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 13 | 14 | public class JvmMetricsInfoTest { 15 | @Test 16 | public void testJvmInfoMetrics() { 17 | MeterRegistry registry = new SimpleMeterRegistry(); 18 | new JvmInfoMetrics().bindTo(registry); 19 | 20 | Collection gauges = Search.in(registry).name("jvm.info").gauges(); 21 | Assertions.assertEquals(1, gauges.size(), 22 | "Should find one jvm.info gauge"); 23 | 24 | Gauge jvmInfo = gauges.iterator().next(); 25 | Assertions.assertEquals(1L, jvmInfo.value(), 26 | "jvm.info gauge should always return 1"); 27 | 28 | Meter.Id id = jvmInfo.getId(); 29 | Assertions.assertNotNull(id.getTag("version"), 30 | "JVM version tag should be defined"); 31 | Assertions.assertNotNull(id.getTag("vendor"), 32 | "JVM vendor tag should be defined"); 33 | Assertions.assertNotNull(id.getTag("runtime"), 34 | "JVM runtime tag should be defined"); 35 | } 36 | } 37 | --------------------------------------------------------------------------------