├── .editorconfig ├── .gitignore ├── src ├── main │ ├── java │ │ ├── io │ │ │ └── vertx │ │ │ │ └── micrometer │ │ │ │ ├── MatchType.java │ │ │ │ ├── package-info.java │ │ │ │ ├── impl │ │ │ │ ├── HttpUtils.java │ │ │ │ ├── MicrometerMetrics.java │ │ │ │ ├── meters │ │ │ │ │ ├── LongGauges.java │ │ │ │ │ ├── LongAdderSupplier.java │ │ │ │ │ └── LongGaugeBuilder.java │ │ │ │ ├── tags │ │ │ │ │ └── Labels.java │ │ │ │ ├── AbstractMetrics.java │ │ │ │ ├── PrometheusScrapingHandlerImpl.java │ │ │ │ ├── PrometheusRequestHandlerImpl.java │ │ │ │ ├── VertxDatagramSocketMetrics.java │ │ │ │ ├── VertxClientMetrics.java │ │ │ │ ├── VertxPoolMetrics.java │ │ │ │ ├── VertxNetServerMetrics.java │ │ │ │ ├── VertxNetClientMetrics.java │ │ │ │ ├── MetricsServiceImpl.java │ │ │ │ └── VertxHttpClientMetrics.java │ │ │ │ ├── backends │ │ │ │ ├── BackendRegistry.java │ │ │ │ ├── NoopBackendRegistry.java │ │ │ │ ├── JmxBackendRegistry.java │ │ │ │ ├── InfluxDbBackendRegistry.java │ │ │ │ ├── PrometheusBackendRegistry.java │ │ │ │ └── BackendRegistries.java │ │ │ │ ├── MetricsDomain.java │ │ │ │ ├── MetricsService.java │ │ │ │ ├── PrometheusScrapingHandler.java │ │ │ │ ├── Label.java │ │ │ │ ├── PrometheusRequestHandler.java │ │ │ │ ├── Match.java │ │ │ │ ├── MicrometerMetricsFactory.java │ │ │ │ ├── VertxJmxMetricsOptions.java │ │ │ │ └── VertxPrometheusOptions.java │ │ └── module-info.java │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── io.vertx.core.spi.VertxServiceProvider │ └── generated │ │ └── io │ │ └── vertx │ │ └── micrometer │ │ ├── VertxJmxMetricsOptionsConverter.java │ │ ├── VertxPrometheusOptionsConverter.java │ │ ├── VertxInfluxDbOptionsConverter.java │ │ └── MicrometerMetricsOptionsConverter.java └── test │ └── java │ └── io │ └── vertx │ └── micrometer │ └── tests │ ├── MyVerticle.java │ ├── MetricsNamingTest.java │ ├── EmptyCompositeMeterRegistryTest.java │ ├── backend │ ├── InfluxDbTestHelper.java │ ├── JmxMetricsITest.java │ ├── InfluxDbReporterITest.java │ ├── PrometheusTestHelper.java │ └── CustomMicrometerMetricsITest.java │ ├── VertxHttpServerMetricsTest.java │ ├── impl │ └── meters │ │ ├── CountersTest.java │ │ ├── TimersTest.java │ │ ├── SummariesTest.java │ │ └── GaugesTest.java │ ├── UnixSocketTest.java │ ├── MatchersTest.java │ ├── VertxDatagramNetClientNetServerSocketMetricsTest.java │ ├── VertxClientMetricsTest.java │ ├── VertxHttpServerMetricsConfigTest.java │ ├── VertxPoolMetricsTest.java │ ├── VertxNetClientServerMetricsTest.java │ ├── ExternalConfigurationTest.java │ ├── MicrometerMetricsTestBase.java │ └── VertxEventBusMetricsTest.java ├── .github ├── workflows │ ├── ci-5.x.yml │ ├── ci-5.x-stable.yml │ ├── ci-4.x.yml │ ├── ci.yml │ ├── ci-matrix-5.x.yml │ └── deploy.yml ├── dependabot.yml ├── maven-ci-settings.xml └── maven-cd-settings.xml ├── log-test.properties └── README.adoc /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .gradle 3 | .idea 4 | .classpath 5 | .project 6 | .settings 7 | .yardoc 8 | .yardopts 9 | build 10 | target 11 | out 12 | *.iml 13 | *.ipr 14 | *.iws 15 | .vertx 16 | test-output 17 | src/scratchpad 18 | test-results 19 | test-tmp 20 | *.class 21 | *.swp 22 | .vertx 23 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/MatchType.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer; 2 | 3 | import io.vertx.codegen.annotations.VertxGen; 4 | 5 | /** 6 | * The type of match. 7 | * 8 | * @author Julien Viet 9 | */ 10 | @VertxGen 11 | public enum MatchType { 12 | EQUALS, 13 | REGEX 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-micrometer-metrics (5.x) 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | schedule: 10 | - cron: '0 5 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x-stable.yml: -------------------------------------------------------------------------------- 1 | name: vertx-micrometer-metrics (5.x-stable) 2 | on: 3 | push: 4 | branches: 5 | - '5.[0-9]+' 6 | pull_request: 7 | branches: 8 | - '5.[0-9]+' 9 | schedule: 10 | - cron: '0 6 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event_name == 'schedule' && vars.VERTX_5_STABLE_BRANCH || github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 Red Hat, Inc. 3 | # 4 | # All rights reserved. This program and the accompanying materials 5 | # are made available under the terms of the Eclipse Public License v1.0 6 | # and Apache License v2.0 which accompanies this distribution. 7 | # 8 | # The Eclipse Public License is available at 9 | # http://www.eclipse.org/legal/epl-v10.html 10 | # 11 | # The Apache License v2.0 is available at 12 | # http://www.opensource.org/licenses/apache2.0.php 13 | # 14 | # You may elect to redistribute this code under either of these licenses. 15 | # 16 | 17 | io.vertx.micrometer.MicrometerMetricsFactory 18 | -------------------------------------------------------------------------------- /.github/workflows/ci-4.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-micrometer-metrics (4.x) 2 | on: 3 | schedule: 4 | - cron: '0 4 * * *' 5 | jobs: 6 | CI: 7 | strategy: 8 | matrix: 9 | include: 10 | - os: ubuntu-latest 11 | jdk: 8 12 | - os: ubuntu-latest 13 | jdk: 17 14 | uses: ./.github/workflows/ci.yml 15 | with: 16 | branch: 4.x 17 | jdk: ${{ matrix.jdk }} 18 | os: ${{ matrix.os }} 19 | secrets: inherit 20 | Deploy: 21 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }} 22 | needs: CI 23 | uses: ./.github/workflows/deploy.yml 24 | with: 25 | branch: 4.x 26 | jdk: 8 27 | secrets: inherit 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | os: 12 | default: ubuntu-latest 13 | type: string 14 | jobs: 15 | Test: 16 | name: Run tests 17 | runs-on: ${{ inputs.os }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Run tests 29 | run: mvn -s .github/maven-ci-settings.xml -q clean verify -B 30 | -------------------------------------------------------------------------------- /log-test.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 Red Hat, Inc. 3 | # 4 | # All rights reserved. This program and the accompanying materials 5 | # are made available under the terms of the Eclipse Public License v1.0 6 | # and Apache License v2.0 which accompanies this distribution. 7 | # 8 | # The Eclipse Public License is available at 9 | # http://www.eclipse.org/legal/epl-v10.html 10 | # 11 | # The Apache License v2.0 is available at 12 | # http://www.opensource.org/licenses/apache2.0.php 13 | # 14 | # You may elect to redistribute this code under either of these licenses. 15 | # 16 | 17 | handlers=java.util.logging.ConsoleHandler 18 | 19 | .level=INFO 20 | 21 | java.util.logging.ConsoleHandler.level=ALL 22 | java.util.logging.ConsoleHandler.formatter=io.vertx.core.logging.VertxLoggerFormatter 23 | 24 | io.vertx.micrometer.level=ALL 25 | -------------------------------------------------------------------------------- /.github/workflows/ci-matrix-5.x.yml: -------------------------------------------------------------------------------- 1 | name: CI matrix (5.x) 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jobs: 9 | CI: 10 | strategy: 11 | matrix: 12 | include: 13 | - os: ubuntu-latest 14 | jdk: 11 15 | - os: ubuntu-latest 16 | jdk: 21 17 | uses: ./.github/workflows/ci.yml 18 | with: 19 | branch: ${{ inputs.branch }} 20 | jdk: ${{ matrix.jdk }} 21 | os: ${{ matrix.os }} 22 | secrets: inherit 23 | Deploy: 24 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }} 25 | needs: CI 26 | uses: ./.github/workflows/deploy.yml 27 | with: 28 | branch: ${{ inputs.branch }} 29 | jdk: 11 30 | secrets: inherit 31 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2017 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | @ModuleGen(name = "vertx-micrometer-metrics", groupPackage = "io.vertx") 18 | package io.vertx.micrometer; 19 | 20 | import io.vertx.codegen.annotations.ModuleGen; 21 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/MyVerticle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.micrometer.tests; 18 | 19 | import io.vertx.core.AbstractVerticle; 20 | 21 | public class MyVerticle extends AbstractVerticle { 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module io.vertx.metrics.micrometer { 2 | 3 | requires static io.vertx.docgen; 4 | requires static io.vertx.codegen.api; 5 | requires static io.vertx.codegen.json; 6 | 7 | requires io.netty.buffer; 8 | requires io.vertx.core; 9 | requires io.vertx.core.logging; 10 | requires io.vertx.web; 11 | requires micrometer.core; 12 | 13 | // Required only at compilation (users can pick the backends they want) 14 | requires static micrometer.registry.graphite; 15 | requires static micrometer.registry.influx; 16 | requires static micrometer.registry.jmx; 17 | requires static micrometer.registry.prometheus; 18 | requires static io.prometheus.metrics.model; 19 | 20 | exports io.vertx.micrometer; 21 | exports io.vertx.micrometer.backends; 22 | 23 | provides io.vertx.core.spi.VertxServiceProvider with io.vertx.micrometer.MicrometerMetricsFactory; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = vertx-micrometer-metrics 2 | :source-language: java 3 | 4 | image:https://github.com/vert-x3/vertx-micrometer-metrics/actions/workflows/ci-5.x.yml/badge.svg["Build Status (5.x)",link="https://github.com/vert-x3/vertx-micrometer-metrics/actions/workflows/ci-5.x.yml"] 5 | image:https://github.com/vert-x3/vertx-micrometer-metrics/actions/workflows/ci-4.x.yml/badge.svg["Build Status (4.x)",link="https://github.com/vert-x3/vertx-micrometer-metrics/actions/workflows/ci-4.x.yml"] 6 | 7 | Vert.x Micrometer Metrics contains metrics collection and reporting to various target systems through link:http://micrometer.io/[Micrometer]. 8 | 9 | The http://vertx.io/docs/vertx-core/java/index.html#_metrics_spi[Vert.x Metrics SPI] allows implementers to 10 | capture events from Vert.x in order to gather metrics. 11 | 12 | Please see the https://vertx.io/docs/vertx-micrometer-metrics/java/[documentation] 13 | on the web-site for a full description. 14 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer.impl; 2 | 3 | /** 4 | * Copied over from Vert.x core HttpUtils to avoid exposing internal utilities. 5 | */ 6 | class HttpUtils { 7 | 8 | /** 9 | * Extract the path out of the uri. 10 | */ 11 | static String parsePath(String uri) { 12 | if (uri.isEmpty()) { 13 | return ""; 14 | } 15 | int i; 16 | if (uri.charAt(0) == '/') { 17 | i = 0; 18 | } else { 19 | i = uri.indexOf("://"); 20 | if (i == -1) { 21 | i = 0; 22 | } else { 23 | i = uri.indexOf('/', i + 3); 24 | if (i == -1) { 25 | // contains no / 26 | return "/"; 27 | } 28 | } 29 | } 30 | 31 | int queryStart = uri.indexOf('?', i); 32 | if (queryStart == -1) { 33 | queryStart = uri.length(); 34 | if (i == 0) { 35 | return uri; 36 | } 37 | } 38 | return uri.substring(i, queryStart); 39 | } 40 | 41 | private HttpUtils() { 42 | // Utility 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/MicrometerMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.impl; 18 | 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | 21 | /** 22 | * Interface for micrometer metrics container. 23 | * 24 | * @author Joel Takvorian 25 | */ 26 | public interface MicrometerMetrics { 27 | /** 28 | * @return the Micrometer registry used with this measured object. 29 | */ 30 | MeterRegistry registry(); 31 | String baseName(); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/backends/BackendRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.backends; 18 | 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | 21 | /** 22 | * @author Joel Takvorian 23 | */ 24 | public interface BackendRegistry { 25 | MeterRegistry getMeterRegistry(); 26 | default void init() {} 27 | default void close() {} 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | jobs: 12 | Deploy: 13 | name: Deploy to OSSRH 14 | runs-on: ubuntu-latest 15 | env: 16 | VERTX_NEXUS_USERNAME: ${{ secrets.VERTX_NEXUS_USERNAME }} 17 | VERTX_NEXUS_PASSWORD: ${{ secrets.VERTX_NEXUS_PASSWORD }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Get project version 29 | run: echo "PROJECT_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout | grep -v '\[')" >> $GITHUB_ENV 30 | - name: Maven deploy 31 | if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }} 32 | run: mvn deploy -s .github/maven-cd-settings.xml -DskipTests -B 33 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/MetricsNamingTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer.tests; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import io.vertx.micrometer.MetricsNaming; 5 | import org.junit.Test; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class MetricsNamingTest { 10 | 11 | private static int NB_METRICS = 39; 12 | 13 | @Test 14 | public void v3NamesShouldCoverAllMetrics() { 15 | MetricsNaming names = MetricsNaming.v3Names(); 16 | JsonObject json = names.toJson(); 17 | assertThat(json.size()).isEqualTo(NB_METRICS); 18 | json.forEach(entry -> assertThat(entry.getValue()).isNotNull()); 19 | } 20 | 21 | @Test 22 | public void v4NamesShouldCoverAllMetrics() { 23 | MetricsNaming names = MetricsNaming.v4Names(); 24 | JsonObject json = names.toJson(); 25 | assertThat(json.size()).isEqualTo(NB_METRICS); 26 | json.forEach(entry -> assertThat(entry.getValue()).isNotNull()); 27 | } 28 | 29 | @Test 30 | public void copyCtorShouldCoverAllMetrics() { 31 | MetricsNaming names = new MetricsNaming(MetricsNaming.v4Names()); 32 | JsonObject json = names.toJson(); 33 | assertThat(json.size()).isEqualTo(NB_METRICS); 34 | json.forEach(entry -> assertThat(entry.getValue()).isNotNull()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/backends/NoopBackendRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.backends; 18 | 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | import io.micrometer.core.instrument.Metrics; 21 | 22 | /** 23 | * @author Joel Takvorian 24 | */ 25 | public enum NoopBackendRegistry implements BackendRegistry { 26 | INSTANCE; 27 | 28 | @Override 29 | public MeterRegistry getMeterRegistry() { 30 | return Metrics.globalRegistry; 31 | } 32 | 33 | @Override 34 | public void init() { 35 | } 36 | 37 | @Override 38 | public void close() { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/meters/LongGauges.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.impl.meters; 19 | 20 | import io.micrometer.core.instrument.Meter; 21 | 22 | import java.util.concurrent.ConcurrentMap; 23 | import java.util.concurrent.atomic.LongAdder; 24 | import java.util.function.ToDoubleFunction; 25 | 26 | public class LongGauges { 27 | 28 | private final ConcurrentMap longGauges; 29 | 30 | public LongGauges(ConcurrentMap longGauges) { 31 | this.longGauges = longGauges; 32 | } 33 | 34 | public LongGaugeBuilder builder(String name, ToDoubleFunction func) { 35 | return new LongGaugeBuilder(name, longGauges, func); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration to keep micrometer up to date 2 | # The requirements were defined in https://github.com/vert-x3/vertx-micrometer-metrics/issues/267 3 | version: 2 4 | updates: 5 | # Propose PRs for all the release types in the master branch. 6 | - package-ecosystem: "maven" 7 | directory: "/" 8 | target-branch: master 9 | schedule: 10 | interval: "weekly" 11 | allow: 12 | - dependency-name: "io.micrometer:*" 13 | 14 | # Propose only minor and patch updates for the 4.x and 5.0 branches. 15 | - package-ecosystem: "maven" 16 | directory: "/" 17 | target-branch: 4.x 18 | schedule: 19 | interval: "weekly" 20 | allow: 21 | - dependency-name: "io.micrometer:*" 22 | ignore: 23 | - dependency-name: "io.micrometer:*" 24 | update-types: 25 | - "version-update:semver-major" 26 | # We need to ignore minor versions as well due to https://github.com/micrometer-metrics/micrometer/issues/6555 27 | - "version-update:semver-minor" 28 | 29 | - package-ecosystem: "maven" 30 | directory: "/" 31 | target-branch: "5.0" 32 | schedule: 33 | interval: "weekly" 34 | allow: 35 | - dependency-name: "io.micrometer:*" 36 | ignore: 37 | - dependency-name: "io.micrometer:*" 38 | update-types: 39 | - "version-update:semver-major" 40 | # We need to ignore minor versions as well due to https://github.com/micrometer-metrics/micrometer/issues/6555 41 | - "version-update:semver-minor" 42 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/backends/JmxBackendRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.backends; 18 | 19 | import io.micrometer.core.instrument.Clock; 20 | import io.micrometer.core.instrument.MeterRegistry; 21 | import io.micrometer.jmx.JmxMeterRegistry; 22 | import io.vertx.micrometer.VertxJmxMetricsOptions; 23 | 24 | /** 25 | * @author Joel Takvorian 26 | */ 27 | public final class JmxBackendRegistry implements BackendRegistry { 28 | private final JmxMeterRegistry registry; 29 | 30 | public JmxBackendRegistry(VertxJmxMetricsOptions options) { 31 | registry = new JmxMeterRegistry(options.toMicrometerConfig(), Clock.SYSTEM); 32 | } 33 | 34 | @Override 35 | public MeterRegistry getMeterRegistry() { 36 | return registry; 37 | } 38 | 39 | @Override 40 | public void init() { 41 | } 42 | 43 | @Override 44 | public void close() { 45 | registry.stop(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/generated/io/vertx/micrometer/VertxJmxMetricsOptionsConverter.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import io.vertx.core.json.JsonArray; 5 | 6 | /** 7 | * Converter and mapper for {@link io.vertx.micrometer.VertxJmxMetricsOptions}. 8 | * NOTE: This class has been automatically generated from the {@link io.vertx.micrometer.VertxJmxMetricsOptions} original class using Vert.x codegen. 9 | */ 10 | public class VertxJmxMetricsOptionsConverter { 11 | 12 | static void fromJson(Iterable> json, VertxJmxMetricsOptions obj) { 13 | for (java.util.Map.Entry member : json) { 14 | switch (member.getKey()) { 15 | case "enabled": 16 | if (member.getValue() instanceof Boolean) { 17 | obj.setEnabled((Boolean)member.getValue()); 18 | } 19 | break; 20 | case "domain": 21 | if (member.getValue() instanceof String) { 22 | obj.setDomain((String)member.getValue()); 23 | } 24 | break; 25 | case "step": 26 | if (member.getValue() instanceof Number) { 27 | obj.setStep(((Number)member.getValue()).intValue()); 28 | } 29 | break; 30 | } 31 | } 32 | } 33 | 34 | static void toJson(VertxJmxMetricsOptions obj, JsonObject json) { 35 | toJson(obj, json.getMap()); 36 | } 37 | 38 | static void toJson(VertxJmxMetricsOptions obj, java.util.Map json) { 39 | json.put("enabled", obj.isEnabled()); 40 | if (obj.getDomain() != null) { 41 | json.put("domain", obj.getDomain()); 42 | } 43 | json.put("step", obj.getStep()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/backends/InfluxDbBackendRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.backends; 18 | 19 | import io.micrometer.core.instrument.Clock; 20 | import io.micrometer.core.instrument.MeterRegistry; 21 | import io.micrometer.influx.InfluxMeterRegistry; 22 | import io.vertx.micrometer.VertxInfluxDbOptions; 23 | 24 | /** 25 | * @author Joel Takvorian 26 | */ 27 | public final class InfluxDbBackendRegistry implements BackendRegistry { 28 | private final InfluxMeterRegistry registry; 29 | 30 | public InfluxDbBackendRegistry(VertxInfluxDbOptions options) { 31 | registry = new InfluxMeterRegistry(options.toMicrometerConfig(), Clock.SYSTEM); 32 | registry.stop(); 33 | } 34 | 35 | @Override 36 | public MeterRegistry getMeterRegistry() { 37 | return registry; 38 | } 39 | 40 | @Override 41 | public void init() { 42 | registry.start(); 43 | } 44 | 45 | @Override 46 | public void close() { 47 | registry.close(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/EmptyCompositeMeterRegistryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests; 19 | 20 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 21 | import io.vertx.ext.unit.TestContext; 22 | import io.vertx.ext.unit.junit.VertxUnitRunner; 23 | import io.vertx.micrometer.MicrometerMetricsOptions; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | 27 | @RunWith(VertxUnitRunner.class) 28 | public class EmptyCompositeMeterRegistryTest extends MicrometerMetricsTestBase { 29 | 30 | @Test 31 | public void simplyStarts(TestContext ctx) { 32 | vertx = vertx(ctx); 33 | 34 | meterRegistry = new CompositeMeterRegistry(); 35 | metricsOptions = new MicrometerMetricsOptions() 36 | .setRegistryName(registryName) 37 | .setEnabled(true); 38 | 39 | // If the task is executed then the gauge lookup succedeed 40 | vertx.executeBlocking(() -> null).onComplete(ctx.asyncAssertSuccess()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/meters/LongAdderSupplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.impl.meters; 19 | 20 | import io.micrometer.core.instrument.Meter; 21 | 22 | import java.util.concurrent.ConcurrentMap; 23 | import java.util.concurrent.atomic.LongAdder; 24 | import java.util.function.Supplier; 25 | import java.util.function.ToDoubleFunction; 26 | 27 | class LongAdderSupplier implements Supplier { 28 | 29 | private final ConcurrentMap longGauges; 30 | private final ToDoubleFunction func; 31 | private volatile Meter.Id id; 32 | 33 | LongAdderSupplier(ConcurrentMap longGauges, ToDoubleFunction func) { 34 | this.longGauges = longGauges; 35 | this.func = func; 36 | } 37 | 38 | void setId(Meter.Id id) { 39 | this.id = id; 40 | } 41 | 42 | @Override 43 | public Number get() { 44 | Meter.Id key = id; 45 | if (key != null) { 46 | LongAdder longAdder = longGauges.get(key); 47 | if (longAdder != null) { 48 | return func.applyAsDouble(longAdder); 49 | } 50 | } 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/tags/Labels.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.impl.tags; 18 | 19 | import io.micrometer.core.instrument.Tag; 20 | import io.vertx.core.net.SocketAddress; 21 | import io.vertx.micrometer.Label; 22 | 23 | /** 24 | * @author Joel Takvorian 25 | */ 26 | public final class Labels { 27 | 28 | private static final Tag LOCAL = Tag.of(Label.EB_SIDE.toString(), "local"); 29 | private static final Tag REMOTE = Tag.of(Label.EB_SIDE.toString(), "remote"); 30 | 31 | private Labels() { 32 | // Utility 33 | } 34 | 35 | public static String address(SocketAddress address) { 36 | return address(address, null); 37 | } 38 | 39 | public static String address(SocketAddress address, String nameOverride) { 40 | if (address == null) { 41 | return "?"; 42 | } 43 | if (nameOverride == null) { 44 | return address.toString(); 45 | } 46 | SocketAddress addrOverride = address.port() >= 0 ? SocketAddress.inetSocketAddress(address.port(), nameOverride) 47 | : SocketAddress.domainSocketAddress(nameOverride); 48 | return addrOverride.toString(); 49 | } 50 | 51 | public static Tag side(boolean local) { 52 | return local ? LOCAL : REMOTE; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.github/maven-ci-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | google-mirror 24 | 25 | true 26 | 27 | 28 | 29 | google-maven-central 30 | GCS Maven Central mirror EU 31 | https://maven-central.storage-download.googleapis.com/maven2/ 32 | 33 | true 34 | 35 | 36 | false 37 | 38 | 39 | 40 | 41 | 42 | google-maven-central 43 | GCS Maven Central mirror 44 | https://maven-central.storage-download.googleapis.com/maven2/ 45 | 46 | true 47 | 48 | 49 | false 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/MetricsDomain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2017 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.micrometer; 17 | 18 | import io.vertx.codegen.annotations.VertxGen; 19 | 20 | 21 | /** 22 | * Metric domains with their associated prefixes. 23 | */ 24 | @VertxGen 25 | public enum MetricsDomain { 26 | 27 | /** 28 | * Net server metrics. 29 | */ 30 | NET_SERVER("vertx.net.server."), 31 | /** 32 | * Net client metrics. 33 | */ 34 | NET_CLIENT("vertx.net.client."), 35 | /** 36 | * Http server metrics. 37 | */ 38 | HTTP_SERVER("vertx.http.server."), 39 | /** 40 | * Http client metrics. 41 | */ 42 | HTTP_CLIENT("vertx.http.client."), 43 | /** 44 | * Datagram socket metrics. 45 | */ 46 | DATAGRAM_SOCKET("vertx.datagram."), 47 | /** 48 | * Event bus metrics. 49 | */ 50 | EVENT_BUS("vertx.eventbus."), 51 | /** 52 | * Named pools metrics. 53 | */ 54 | NAMED_POOLS("vertx.pool."), 55 | /** 56 | * Verticle metrics. 57 | * 58 | * @deprecated as of 5.1, because it's no longer used 59 | */ 60 | @Deprecated(forRemoval = true) 61 | VERTICLES("vertx.verticle."); 62 | 63 | private String prefix; 64 | 65 | MetricsDomain(String prefix) { 66 | this.prefix = prefix; 67 | } 68 | 69 | public String getPrefix() { 70 | return prefix; 71 | } 72 | 73 | public String toCategory() { 74 | // E.g. "vertx.net.server." => "net.server" 75 | return prefix.substring(6, prefix.length() - 1); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /.github/maven-cd-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | vertx-snapshots-repository 24 | ${env.VERTX_NEXUS_USERNAME} 25 | ${env.VERTX_NEXUS_PASSWORD} 26 | 27 | 28 | 29 | 30 | 31 | google-mirror 32 | 33 | true 34 | 35 | 36 | 37 | google-maven-central 38 | GCS Maven Central mirror EU 39 | https://maven-central.storage-download.googleapis.com/maven2/ 40 | 41 | true 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | google-maven-central 51 | GCS Maven Central mirror 52 | https://maven-central.storage-download.googleapis.com/maven2/ 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/meters/LongGaugeBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.impl.meters; 19 | 20 | import io.micrometer.core.instrument.Gauge; 21 | import io.micrometer.core.instrument.Meter; 22 | import io.micrometer.core.instrument.MeterRegistry; 23 | import io.micrometer.core.instrument.Tag; 24 | 25 | import java.util.concurrent.ConcurrentMap; 26 | import java.util.concurrent.atomic.LongAdder; 27 | import java.util.function.Supplier; 28 | import java.util.function.ToDoubleFunction; 29 | 30 | public class LongGaugeBuilder { 31 | 32 | private final LongAdderSupplier supplier; 33 | private final Gauge.Builder> builder; 34 | private final ConcurrentMap longGauges; 35 | 36 | LongGaugeBuilder(String name, ConcurrentMap longGauges, ToDoubleFunction func) { 37 | this.supplier = new LongAdderSupplier(longGauges, func); 38 | this.builder = Gauge.builder(name, supplier); 39 | this.longGauges = longGauges; 40 | } 41 | 42 | public LongGaugeBuilder description(String description) { 43 | builder.description(description); 44 | return this; 45 | } 46 | 47 | public LongGaugeBuilder tags(Iterable tags) { 48 | builder.tags(tags); 49 | return this; 50 | } 51 | 52 | public LongAdder register(MeterRegistry registry) { 53 | Meter.Id meterId = builder.register(registry).getId(); 54 | supplier.setId(meterId); 55 | return longGauges.computeIfAbsent(meterId, id -> new LongAdder()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/backend/InfluxDbTestHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.tests.backend; 18 | 19 | import io.vertx.core.Vertx; 20 | import io.vertx.core.http.HttpServerOptions; 21 | import io.vertx.ext.unit.Async; 22 | import io.vertx.ext.unit.TestContext; 23 | 24 | import java.util.function.Consumer; 25 | 26 | /** 27 | * @author Joel Takvorian 28 | */ 29 | public final class InfluxDbTestHelper { 30 | private InfluxDbTestHelper() { 31 | } 32 | 33 | static void simulateInfluxServer(Vertx vertx, TestContext context, int port, Consumer onRequest) { 34 | Async ready = context.async(); 35 | vertx.runOnContext(v -> vertx.createHttpServer(new HttpServerOptions() 36 | .setCompressionSupported(true) 37 | .setDecompressionSupported(true) 38 | .setLogActivity(true) 39 | .setHost("localhost") 40 | .setPort(port)) 41 | .requestHandler(req -> { 42 | req.exceptionHandler(context.exceptionHandler()); 43 | req.bodyHandler(buffer -> { 44 | String str = buffer.toString(); 45 | if (str.isEmpty()) { 46 | req.response().setStatusCode(200).end(); 47 | return; 48 | } 49 | try { 50 | onRequest.accept(str); 51 | } finally { 52 | req.response().setStatusCode(200).end(); 53 | } 54 | }); 55 | }) 56 | .exceptionHandler(System.err::println) 57 | .listen(port, "localhost").onComplete(res -> { 58 | if (res.succeeded()) { 59 | ready.complete(); 60 | } else { 61 | context.fail(res.cause()); 62 | } 63 | })); 64 | ready.await(10000); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/generated/io/vertx/micrometer/VertxPrometheusOptionsConverter.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import io.vertx.core.json.JsonArray; 5 | 6 | /** 7 | * Converter and mapper for {@link io.vertx.micrometer.VertxPrometheusOptions}. 8 | * NOTE: This class has been automatically generated from the {@link io.vertx.micrometer.VertxPrometheusOptions} original class using Vert.x codegen. 9 | */ 10 | public class VertxPrometheusOptionsConverter { 11 | 12 | static void fromJson(Iterable> json, VertxPrometheusOptions obj) { 13 | for (java.util.Map.Entry member : json) { 14 | switch (member.getKey()) { 15 | case "enabled": 16 | if (member.getValue() instanceof Boolean) { 17 | obj.setEnabled((Boolean)member.getValue()); 18 | } 19 | break; 20 | case "startEmbeddedServer": 21 | if (member.getValue() instanceof Boolean) { 22 | obj.setStartEmbeddedServer((Boolean)member.getValue()); 23 | } 24 | break; 25 | case "embeddedServerOptions": 26 | if (member.getValue() instanceof JsonObject) { 27 | obj.setEmbeddedServerOptions(new io.vertx.core.http.HttpServerOptions((io.vertx.core.json.JsonObject)member.getValue())); 28 | } 29 | break; 30 | case "embeddedServerEndpoint": 31 | if (member.getValue() instanceof String) { 32 | obj.setEmbeddedServerEndpoint((String)member.getValue()); 33 | } 34 | break; 35 | case "publishQuantiles": 36 | if (member.getValue() instanceof Boolean) { 37 | obj.setPublishQuantiles((Boolean)member.getValue()); 38 | } 39 | break; 40 | } 41 | } 42 | } 43 | 44 | static void toJson(VertxPrometheusOptions obj, JsonObject json) { 45 | toJson(obj, json.getMap()); 46 | } 47 | 48 | static void toJson(VertxPrometheusOptions obj, java.util.Map json) { 49 | json.put("enabled", obj.isEnabled()); 50 | json.put("startEmbeddedServer", obj.isStartEmbeddedServer()); 51 | if (obj.getEmbeddedServerOptions() != null) { 52 | json.put("embeddedServerOptions", obj.getEmbeddedServerOptions().toJson()); 53 | } 54 | if (obj.getEmbeddedServerEndpoint() != null) { 55 | json.put("embeddedServerEndpoint", obj.getEmbeddedServerEndpoint()); 56 | } 57 | json.put("publishQuantiles", obj.isPublishQuantiles()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/VertxHttpServerMetricsTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer.tests; 2 | 3 | import io.vertx.core.http.HttpClient; 4 | import io.vertx.core.http.HttpClientResponse; 5 | import io.vertx.core.http.HttpMethod; 6 | import io.vertx.core.http.HttpServer; 7 | import io.vertx.ext.unit.Async; 8 | import io.vertx.ext.unit.TestContext; 9 | import io.vertx.ext.unit.junit.VertxUnitRunner; 10 | import io.vertx.micrometer.Label; 11 | import io.vertx.micrometer.MicrometerMetricsOptions; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | import java.util.List; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | 19 | @RunWith(VertxUnitRunner.class) 20 | public class VertxHttpServerMetricsTest extends MicrometerMetricsTestBase { 21 | 22 | private HttpServer httpServer; 23 | 24 | @Override 25 | protected MicrometerMetricsOptions metricOptions() { 26 | return super.metricOptions() 27 | .addLabels(Label.HTTP_PATH); 28 | } 29 | 30 | @Test 31 | public void shouldDecrementActiveRequestsWhenRequestEndedAfterResponseEnded(TestContext ctx) { 32 | vertx = vertx(ctx); 33 | int numRequests = 10; 34 | Async doneLatch = ctx.async(numRequests * 2); 35 | httpServer = vertx.createHttpServer() 36 | .requestHandler(req -> { 37 | req.response().end(); 38 | req.end().onComplete(ctx.asyncAssertSuccess(v -> doneLatch.countDown())); 39 | }); 40 | Async listenLatch = ctx.async(); 41 | httpServer 42 | .listen(9195, "127.0.0.1") 43 | .onComplete(ctx.asyncAssertSuccess(s -> listenLatch.complete())); 44 | listenLatch.awaitSuccess(20_000); 45 | HttpClient client = vertx.createHttpClient(); 46 | for (int i = 0;i < numRequests;i++) { 47 | client.request(HttpMethod.POST, 9195, "127.0.0.1", "/resource?foo=bar") 48 | .onComplete(ctx.asyncAssertSuccess(req -> { 49 | req 50 | .response() 51 | .compose(HttpClientResponse::body) 52 | .onComplete(ctx.asyncAssertSuccess(b -> { 53 | doneLatch.countDown(); 54 | req.end(); 55 | })); 56 | req.setChunked(true); 57 | req.write("chunk"); 58 | })); 59 | } 60 | doneLatch.awaitSuccess(20_000); 61 | List datapoints = listDatapoints(startsWith("vertx.http.server.active.requests")); 62 | assertThat(datapoints).hasSize(1).contains( 63 | dp("vertx.http.server.active.requests[method=POST,path=/resource]$VALUE", 0.0)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/MetricsService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer; 18 | 19 | import io.vertx.codegen.annotations.VertxGen; 20 | import io.vertx.core.json.JsonObject; 21 | import io.vertx.core.metrics.Measured; 22 | import io.vertx.micrometer.impl.MetricsServiceImpl; 23 | 24 | import java.util.Set; 25 | 26 | /** 27 | * The metrics service mainly allows to return a snapshot of measured objects.
28 | * This service is derived and adapted from {@code MetricsService} in the vertx-dropwizard-metrics module. 29 | * 30 | * @author Nick Scavelli 31 | * @author Joel Takvorian 32 | */ 33 | @VertxGen 34 | public interface MetricsService { 35 | 36 | /** 37 | * Creates a metric service for a given {@link Measured} object. 38 | * 39 | * @param measured the measured object 40 | * @return the metrics service 41 | */ 42 | static MetricsService create(Measured measured) { 43 | return new MetricsServiceImpl(measured); 44 | } 45 | 46 | /** 47 | * @return the base name of the measured object 48 | */ 49 | String getBaseName(); 50 | 51 | /** 52 | * @return the known metrics names by this service 53 | */ 54 | Set metricsNames(); 55 | 56 | /** 57 | * Will return the metrics that correspond with the {@code measured} object, null if no metrics is available.

58 | * 59 | * @return the map of metrics where the key is the name of the metric (excluding the base name unless for the Vert.x object) 60 | * and the value is the json data representing that metric 61 | */ 62 | JsonObject getMetricsSnapshot(); 63 | 64 | /** 65 | * Will return the metrics that begins with the {@code baseName}, null if no metrics is available.

66 | * 67 | * @return the map of metrics where the key is the name of the metric and the value is the json data 68 | * representing that metric 69 | */ 70 | JsonObject getMetricsSnapshot(String baseName); 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/backend/JmxMetricsITest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2017 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.tests.backend; 18 | 19 | import io.vertx.ext.unit.Async; 20 | import io.vertx.ext.unit.TestContext; 21 | import io.vertx.ext.unit.junit.VertxUnitRunner; 22 | import io.vertx.micrometer.Label; 23 | import io.vertx.micrometer.MicrometerMetricsOptions; 24 | import io.vertx.micrometer.tests.MicrometerMetricsTestBase; 25 | import io.vertx.micrometer.VertxJmxMetricsOptions; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | import javax.management.MBeanServer; 30 | import javax.management.ObjectName; 31 | import java.lang.management.ManagementFactory; 32 | import java.util.Hashtable; 33 | 34 | import static org.assertj.core.api.Assertions.assertThat; 35 | 36 | @RunWith(VertxUnitRunner.class) 37 | public class JmxMetricsITest extends MicrometerMetricsTestBase { 38 | 39 | @Test 40 | public void shouldReportJmx(TestContext context) throws Exception { 41 | metricsOptions = new MicrometerMetricsOptions() 42 | .setRegistryName(registryName) 43 | .addLabels(Label.EB_ADDRESS) 44 | .setJmxMetricsOptions(new VertxJmxMetricsOptions().setEnabled(true) 45 | .setDomain("my-metrics") 46 | .setStep(1)) 47 | .setEnabled(true); 48 | 49 | vertx = vertx(context); 50 | 51 | // Send something on the eventbus and wait til it's received 52 | Async asyncEB = context.async(); 53 | vertx.eventBus().consumer("test-eb", msg -> asyncEB.complete()); 54 | vertx.eventBus().publish("test-eb", "test message"); 55 | asyncEB.await(2000); 56 | 57 | // Read MBean 58 | MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 59 | assertThat(mbs.getDomains()).contains("my-metrics"); 60 | Hashtable table = new Hashtable<>(); 61 | table.put("type", "gauges"); 62 | table.put("name", "vertxEventbusHandlers.address.test-eb"); 63 | Number result = (Number) mbs.getAttribute(new ObjectName("my-metrics", table), "Value"); 64 | assertThat(result).isEqualTo(1d); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/backend/InfluxDbReporterITest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2017 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.tests.backend; 18 | 19 | 20 | import io.vertx.core.Vertx; 21 | import io.vertx.ext.unit.Async; 22 | import io.vertx.ext.unit.TestContext; 23 | import io.vertx.ext.unit.junit.VertxUnitRunner; 24 | import io.vertx.micrometer.Label; 25 | import io.vertx.micrometer.MicrometerMetricsOptions; 26 | import io.vertx.micrometer.tests.MicrometerMetricsTestBase; 27 | import io.vertx.micrometer.VertxInfluxDbOptions; 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | 31 | @RunWith(VertxUnitRunner.class) 32 | public class InfluxDbReporterITest extends MicrometerMetricsTestBase { 33 | 34 | private Vertx vertxForSimulatedServer = Vertx.vertx(); 35 | 36 | @Override 37 | protected void tearDown(TestContext context) { 38 | super.tearDown(context); 39 | vertxForSimulatedServer.close().onComplete(context.asyncAssertSuccess()); 40 | } 41 | 42 | @Test 43 | public void shouldSendDataToInfluxDb(TestContext context) throws Exception { 44 | // Mock an influxdb server 45 | Async asyncInflux = context.async(); 46 | InfluxDbTestHelper.simulateInfluxServer(vertxForSimulatedServer, context, 8086, body -> { 47 | if (body.contains("vertx_eventbus_handlers,address=test-eb,metric_type=gauge value=1")) { 48 | asyncInflux.complete(); 49 | } 50 | }); 51 | 52 | metricsOptions = new MicrometerMetricsOptions() 53 | .setInfluxDbOptions(new VertxInfluxDbOptions() 54 | .setStep(1) 55 | .setDb("mydb") 56 | .setEnabled(true)) 57 | .setRegistryName(registryName) 58 | .addLabels(Label.EB_ADDRESS) 59 | .setEnabled(true); 60 | 61 | vertx = vertx(context); 62 | 63 | // Send something on the eventbus and wait til it's received 64 | Async asyncEB = context.async(); 65 | vertx.eventBus().consumer("test-eb", msg -> asyncEB.complete()); 66 | vertx.eventBus().publish("test-eb", "test message"); 67 | asyncEB.await(2000); 68 | 69 | // Await influx 70 | asyncInflux.awaitSuccess(2000); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/AbstractMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.impl; 18 | 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | import io.vertx.micrometer.Label; 21 | import io.vertx.micrometer.MetricsDomain; 22 | import io.vertx.micrometer.MetricsNaming; 23 | import io.vertx.micrometer.impl.meters.LongGaugeBuilder; 24 | import io.vertx.micrometer.impl.meters.LongGauges; 25 | 26 | import java.util.EnumSet; 27 | import java.util.concurrent.atomic.LongAdder; 28 | import java.util.function.ToDoubleFunction; 29 | 30 | /** 31 | * Abstract class for metrics container. 32 | * 33 | * @author Joel Takvorian 34 | */ 35 | public abstract class AbstractMetrics implements MicrometerMetrics { 36 | 37 | protected final MeterRegistry registry; 38 | protected final MetricsNaming names; 39 | private final String category; 40 | protected final EnumSet

28 | * This handler can be used to scrape metrics from a PrometheusMeterRegistry and serve them over HTTP. 29 | *

30 | * 31 | * @author Swamy Mavuri 32 | */ 33 | public class PrometheusRequestHandlerImpl implements Handler { 34 | 35 | private final PrometheusMeterRegistry registry; 36 | private final String metricsEndpoint; 37 | 38 | /** 39 | * Constructs a handler with the specified registry and metrics endpoint. 40 | * 41 | * @param registry the PrometheusMeterRegistry to use for scraping metrics 42 | * @param metricsEndpoint the endpoint to expose metrics 43 | */ 44 | public PrometheusRequestHandlerImpl(PrometheusMeterRegistry registry, String metricsEndpoint) { 45 | this.registry = registry; 46 | this.metricsEndpoint = metricsEndpoint; 47 | } 48 | 49 | /** 50 | * Constructs a handler with the specified registry and the default metrics endpoint ("/metrics"). 51 | * 52 | * @param registry the PrometheusMeterRegistry to use for scraping metrics 53 | */ 54 | public PrometheusRequestHandlerImpl(PrometheusMeterRegistry registry) { 55 | this.registry = registry; 56 | this.metricsEndpoint = "/metrics"; 57 | } 58 | 59 | /** 60 | * Constructs a handler with a new PrometheusMeterRegistry and the default metrics endpoint ("/metrics"). 61 | */ 62 | public PrometheusRequestHandlerImpl() { 63 | this.registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); 64 | this.metricsEndpoint = "/metrics"; 65 | } 66 | 67 | @Override 68 | public void handle(HttpServerRequest request) { 69 | if (metricsEndpoint.equals(request.path())) { 70 | request.response() 71 | .putHeader(HttpHeaders.CONTENT_TYPE, "text/plain; version=0.0.4; charset=utf-8") 72 | .end(registry.scrape()); 73 | } else { 74 | request.response().setStatusCode(404).end(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/Label.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer; 18 | 19 | import io.vertx.codegen.annotations.VertxGen; 20 | 21 | /** 22 | * List of labels used in various Vert.x metrics. Labels that may not have bounded values are disabled by default. 23 | * 24 | * @author Joel Takvorian 25 | */ 26 | @VertxGen 27 | public enum Label { 28 | /** 29 | * Local address in client-host or host-client connections (used in net, http and datagram domains) 30 | */ 31 | LOCAL("local"), 32 | /** 33 | * Remote address in client-host or host-client connections (used in net and http domains) 34 | */ 35 | REMOTE("remote"), 36 | /** 37 | * Path of the URI for client or server requests (used in http domain) 38 | */ 39 | HTTP_PATH("path"), 40 | /** 41 | * Route as provided by routing modules to the http requests 42 | */ 43 | HTTP_ROUTE("route"), 44 | /** 45 | * Method (GET, POST, PUT, etc.) of an HTTP requests (used in http domain) 46 | */ 47 | HTTP_METHOD("method"), 48 | /** 49 | * HTTP response code (used in http domain) 50 | */ 51 | HTTP_CODE("code"), 52 | /** 53 | * Class name. When used in error counters (in net, http, datagram and eventbus domains) it relates to an exception 54 | * that occurred. When used in verticle domain, it relates to the verticle class name. 55 | */ 56 | CLASS_NAME("class"), 57 | /** 58 | * Event bus address 59 | */ 60 | EB_ADDRESS("address"), 61 | /** 62 | * Event bus side of the metric, it can be either "local" or "remote" 63 | */ 64 | EB_SIDE("side"), 65 | /** 66 | * Event bus failure name from a ReplyFailure object 67 | */ 68 | EB_FAILURE("failure"), 69 | /** 70 | * Pool type, such as "worker" or "datasource" (used in pools domain) 71 | */ 72 | POOL_TYPE("pool_type"), 73 | /** 74 | * Pool name (used in pools domain) 75 | */ 76 | POOL_NAME("pool_name"), 77 | /** 78 | * Client name, such as Net and HTTP Clients 79 | * @see io.vertx.core.net.ClientOptionsBase#setMetricsName(String) 80 | */ 81 | CLIENT_NAME("client_name"), 82 | /** 83 | * Client namespace 84 | */ 85 | NAMESPACE("client_namespace"); 86 | 87 | private final String labelOutput; 88 | 89 | Label(String labelOutput) { 90 | this.labelOutput = labelOutput; 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return labelOutput; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/VertxDatagramSocketMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.impl; 18 | 19 | import io.micrometer.core.instrument.Counter; 20 | import io.micrometer.core.instrument.DistributionSummary; 21 | import io.micrometer.core.instrument.Meter.MeterProvider; 22 | import io.micrometer.core.instrument.Tags; 23 | import io.vertx.core.net.SocketAddress; 24 | import io.vertx.core.spi.metrics.DatagramSocketMetrics; 25 | import io.vertx.micrometer.impl.tags.Labels; 26 | 27 | import static io.vertx.micrometer.Label.CLASS_NAME; 28 | import static io.vertx.micrometer.Label.LOCAL; 29 | import static io.vertx.micrometer.MetricsDomain.DATAGRAM_SOCKET; 30 | 31 | /** 32 | * @author Joel Takvorian 33 | */ 34 | class VertxDatagramSocketMetrics extends AbstractMetrics implements DatagramSocketMetrics { 35 | 36 | private final DistributionSummary bytesWritten; 37 | private final MeterProvider errorCount; 38 | private volatile DistributionSummary bytesRead; 39 | 40 | VertxDatagramSocketMetrics(AbstractMetrics parent) { 41 | super(parent, DATAGRAM_SOCKET); 42 | bytesWritten = DistributionSummary.builder(names.getDatagramBytesWritten()) 43 | .description("Total number of datagram bytes sent") 44 | .register(registry); 45 | errorCount = Counter.builder(names.getDatagramErrorCount()) 46 | .description("Total number of datagram errors") 47 | .withRegistry(registry); 48 | } 49 | 50 | @Override 51 | public void listening(String localName, SocketAddress localAddress) { 52 | Tags tags; 53 | if (enabledLabels.contains(LOCAL)) { 54 | tags = Tags.of(LOCAL.toString(), Labels.address(localAddress, localName)); 55 | } else { 56 | tags = Tags.empty(); 57 | } 58 | bytesRead = DistributionSummary.builder(names.getDatagramBytesRead()) 59 | .description("Total number of datagram bytes received") 60 | .tags(tags) 61 | .register(registry); 62 | } 63 | 64 | @Override 65 | public void bytesRead(Void socketMetric, SocketAddress remoteAddress, long numberOfBytes) { 66 | if (bytesRead != null) { 67 | bytesRead.record(numberOfBytes); 68 | } 69 | } 70 | 71 | @Override 72 | public void bytesWritten(Void socketMetric, SocketAddress remoteAddress, long numberOfBytes) { 73 | bytesWritten.record(numberOfBytes); 74 | } 75 | 76 | @Override 77 | public void exceptionOccurred(Void socketMetric, SocketAddress remoteAddress, Throwable t) { 78 | Tags tags; 79 | if (enabledLabels.contains(CLASS_NAME)) { 80 | tags = Tags.of(CLASS_NAME.toString(), t.getClass().getSimpleName()); 81 | } else { 82 | tags = Tags.empty(); 83 | } 84 | errorCount.withTags(tags).increment(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/PrometheusRequestHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.micrometer; 18 | import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; 19 | import io.vertx.codegen.annotations.GenIgnore; 20 | import io.vertx.codegen.annotations.VertxGen; 21 | import io.vertx.core.Handler; 22 | import io.vertx.core.http.HttpServerRequest; 23 | import io.vertx.micrometer.impl.PrometheusRequestHandlerImpl; 24 | 25 | /** 26 | * An interface for creating handlers to expose Prometheus metrics via an HTTP endpoint. 27 | *

28 | * This interface provides factory methods to create handlers that can scrape metrics from a 29 | * PrometheusMeterRegistry and serve them over HTTP. It allows for various configurations of 30 | * the metrics endpoint and the Prometheus registry. 31 | *

32 | * 33 | * @see PrometheusMeterRegistry 34 | * @see Handler 35 | * @see HttpServerRequest 36 | * 37 | * @author Swamy Mavuri 38 | */ 39 | @VertxGen 40 | public interface PrometheusRequestHandler { 41 | 42 | /** 43 | * Creates a handler with the specified PrometheusMeterRegistry and metrics endpoint. 44 | *

45 | * This handler scrapes metrics from the given PrometheusMeterRegistry and serves them 46 | * at the specified endpoint. 47 | *

48 | * 49 | * @param registry the PrometheusMeterRegistry to use for scraping metrics 50 | * @param metricsEndpoint the endpoint to expose metrics 51 | * @return a handler for scraping Prometheus metrics 52 | */ 53 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 54 | static Handler create(PrometheusMeterRegistry registry, String metricsEndpoint) { 55 | return new PrometheusRequestHandlerImpl(registry, metricsEndpoint); 56 | } 57 | 58 | /** 59 | * Creates a handler with the specified PrometheusMeterRegistry and the default metrics endpoint ("/metrics"). 60 | *

61 | * This handler scrapes metrics from the given PrometheusMeterRegistry and serves them 62 | * at the default endpoint "/metrics". 63 | *

64 | * 65 | * @param registry the PrometheusMeterRegistry to use for scraping metrics 66 | * @return a handler for scraping Prometheus metrics 67 | */ 68 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 69 | static Handler create(PrometheusMeterRegistry registry) { 70 | return new PrometheusRequestHandlerImpl(registry); 71 | } 72 | 73 | /** 74 | * Creates a handler with a new PrometheusMeterRegistry and the default metrics endpoint ("/metrics"). 75 | *

76 | * This handler scrapes metrics from a newly created PrometheusMeterRegistry and serves them 77 | * at the default endpoint "/metrics". 78 | *

79 | * 80 | * @return a handler for scraping Prometheus metrics 81 | */ 82 | static Handler create() { 83 | return new PrometheusRequestHandlerImpl(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/VertxClientMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.impl; 18 | 19 | import io.micrometer.core.instrument.Counter; 20 | import io.micrometer.core.instrument.Tags; 21 | import io.micrometer.core.instrument.Timer; 22 | import io.micrometer.core.instrument.Timer.Sample; 23 | import io.vertx.core.net.SocketAddress; 24 | import io.vertx.core.spi.metrics.ClientMetrics; 25 | import io.vertx.micrometer.impl.tags.Labels; 26 | 27 | import java.util.concurrent.atomic.LongAdder; 28 | 29 | import static io.vertx.micrometer.Label.NAMESPACE; 30 | import static io.vertx.micrometer.Label.REMOTE; 31 | 32 | /** 33 | * @author Joel Takvorian 34 | */ 35 | class VertxClientMetrics extends AbstractMetrics implements ClientMetrics { 36 | 37 | final Timer processingTime; 38 | final LongAdder processingPending; 39 | final Counter resetCount; 40 | 41 | VertxClientMetrics(AbstractMetrics parent, SocketAddress remoteAddress, String type, String namespace) { 42 | super(parent, type); 43 | Tags tags = Tags.empty(); 44 | if (enabledLabels.contains(REMOTE)) { 45 | tags = tags.and(REMOTE.toString(), Labels.address(remoteAddress)); 46 | } 47 | if (enabledLabels.contains(NAMESPACE)) { 48 | tags = tags.and(NAMESPACE.toString(), namespace == null ? "" : namespace); 49 | } 50 | processingTime = Timer.builder(names.getClientProcessingTime()) 51 | .description("Processing time, from request start to response end") 52 | .tags(tags) 53 | .register(registry); 54 | processingPending = longGaugeBuilder(names.getClientProcessingPending(), LongAdder::doubleValue) 55 | .description("Number of elements being processed") 56 | .tags(tags) 57 | .register(registry); 58 | resetCount = Counter.builder(names.getClientResetsCount()) 59 | .description("Total number of resets") 60 | .tags(tags) 61 | .register(registry); 62 | } 63 | 64 | @Override 65 | public Sample requestBegin(String uri, Object request) { 66 | // Ignore parameters at the moment; need to carefully figure out what can be labelled or not 67 | processingPending.increment(); 68 | return Timer.start(); 69 | } 70 | 71 | @Override 72 | public void requestEnd(Sample requestMetric) { 73 | // Ignoring request-alone metrics at the moment 74 | } 75 | 76 | @Override 77 | public void responseBegin(Sample requestMetric, Object response) { 78 | // Ignoring response-alone metrics at the moment 79 | } 80 | 81 | @Override 82 | public void requestReset(Sample requestMetric) { 83 | processingPending.decrement(); 84 | requestMetric.stop(processingTime); 85 | resetCount.increment(); 86 | } 87 | 88 | @Override 89 | public void responseEnd(Sample requestMetric) { 90 | processingPending.decrement(); 91 | requestMetric.stop(processingTime); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/impl/meters/CountersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests.impl.meters; 19 | 20 | import io.micrometer.core.instrument.Counter; 21 | import io.micrometer.core.instrument.MeterRegistry; 22 | import io.micrometer.core.instrument.Tags; 23 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 24 | import io.vertx.micrometer.Match; 25 | import io.vertx.micrometer.MatchType; 26 | import io.vertx.micrometer.backends.BackendRegistries; 27 | import org.junit.Test; 28 | 29 | import java.util.Collections; 30 | 31 | import static io.vertx.micrometer.Label.EB_ADDRESS; 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | 34 | /** 35 | * @author Joel Takvorian 36 | */ 37 | public class CountersTest { 38 | 39 | @Test 40 | public void shouldAliasCounterLabel() { 41 | MeterRegistry registry = new SimpleMeterRegistry(); 42 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 43 | .setLabel("address") 44 | .setType(MatchType.REGEX) 45 | .setValue("addr1") 46 | .setAlias("1"))); 47 | Counter c1 = Counter.builder("my_counter").tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 48 | c1.increment(); 49 | c1.increment(); 50 | Counter c2 = Counter.builder("my_counter").tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 51 | c2.increment(); 52 | 53 | Counter c = registry.find("my_counter").tags("address", "1").counter(); 54 | assertThat(c).isNotNull().extracting(Counter::count).containsExactly(2d); 55 | c = registry.find("my_counter").tags("address", "addr1").counter(); 56 | assertThat(c).isNull(); 57 | c = registry.find("my_counter").tags("address", "addr2").counter(); 58 | assertThat(c).isNotNull().extracting(Counter::count).containsExactly(1d); 59 | } 60 | 61 | @Test 62 | public void shouldIgnoreCounterLabel() { 63 | MeterRegistry registry = new SimpleMeterRegistry(); 64 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 65 | .setLabel("address") 66 | .setType(MatchType.REGEX) 67 | .setValue(".*") 68 | .setAlias("_"))); 69 | Counter c1 = Counter.builder("my_counter").tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 70 | c1.increment(); 71 | c1.increment(); 72 | Counter c2 = Counter.builder("my_counter").tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 73 | c2.increment(); 74 | 75 | Counter c = registry.find("my_counter").tags("address", "_").counter(); 76 | assertThat(c).isNotNull().extracting(Counter::count).containsExactly(3d); 77 | c = registry.find("my_counter").tags("address", "addr1").counter(); 78 | assertThat(c).isNull(); 79 | c = registry.find("my_counter").tags("address", "addr2").counter(); 80 | assertThat(c).isNull(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/backends/PrometheusBackendRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.backends; 18 | 19 | import io.micrometer.core.instrument.Meter; 20 | import io.micrometer.core.instrument.MeterRegistry; 21 | import io.micrometer.core.instrument.config.MeterFilter; 22 | import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; 23 | import io.micrometer.prometheusmetrics.PrometheusConfig; 24 | import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; 25 | import io.vertx.core.Vertx; 26 | import io.vertx.core.http.HttpServerOptions; 27 | import io.vertx.core.internal.logging.Logger; 28 | import io.vertx.core.internal.logging.LoggerFactory; 29 | import io.vertx.micrometer.PrometheusRequestHandler; 30 | import io.vertx.micrometer.VertxPrometheusOptions; 31 | 32 | /** 33 | * @author Joel Takvorian 34 | */ 35 | public final class PrometheusBackendRegistry implements BackendRegistry { 36 | private static final Logger LOGGER = LoggerFactory.getLogger(PrometheusBackendRegistry.class); 37 | 38 | private final PrometheusMeterRegistry registry; 39 | private final VertxPrometheusOptions options; 40 | private Vertx vertx; 41 | 42 | public PrometheusBackendRegistry(VertxPrometheusOptions options) { 43 | this(options, new PrometheusMeterRegistry(PrometheusConfig.DEFAULT)); 44 | } 45 | 46 | public PrometheusBackendRegistry(VertxPrometheusOptions options, PrometheusMeterRegistry registry) { 47 | this.options = options; 48 | this.registry = registry; 49 | if (options.isPublishQuantiles()) { 50 | registry.config().meterFilter( 51 | new MeterFilter() { 52 | @Override 53 | public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) { 54 | return DistributionStatisticConfig.builder() 55 | .percentilesHistogram(true) 56 | .build() 57 | .merge(config); 58 | } 59 | }); 60 | } 61 | } 62 | 63 | @Override 64 | public MeterRegistry getMeterRegistry() { 65 | return registry; 66 | } 67 | 68 | @Override 69 | public void init() { 70 | if (options.isStartEmbeddedServer()) { 71 | this.vertx = Vertx.vertx(); 72 | // Start dedicated server 73 | HttpServerOptions serverOptions = options.getEmbeddedServerOptions(); 74 | if (serverOptions == null) { 75 | serverOptions = new HttpServerOptions(); 76 | } 77 | vertx.createHttpServer(serverOptions) 78 | .requestHandler(PrometheusRequestHandler.create(registry, options.getEmbeddedServerEndpoint())) 79 | .exceptionHandler(t -> LOGGER.error("Error in Prometheus registry embedded server", t)) 80 | .listen(serverOptions.getPort(), serverOptions.getHost()); 81 | } 82 | } 83 | 84 | @Override 85 | public void close() { 86 | if (this.vertx != null) { 87 | vertx.close(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/UnixSocketTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer.tests; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.Promise; 5 | import io.vertx.core.Vertx; 6 | import io.vertx.core.VertxOptions; 7 | import io.vertx.core.net.NetClient; 8 | import io.vertx.core.net.NetSocket; 9 | import io.vertx.core.net.SocketAddress; 10 | import io.vertx.ext.unit.Async; 11 | import io.vertx.ext.unit.TestContext; 12 | import io.vertx.ext.unit.junit.VertxUnitRunner; 13 | import io.vertx.micrometer.Label; 14 | import io.vertx.micrometer.MetricsDomain; 15 | import io.vertx.micrometer.MicrometerMetricsOptions; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | 19 | import java.util.List; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | @RunWith(VertxUnitRunner.class) 24 | public class UnixSocketTest extends MicrometerMetricsTestBase { 25 | 26 | @Override 27 | protected MicrometerMetricsOptions metricOptions() { 28 | return super.metricOptions() 29 | .addDisabledMetricsCategory(MetricsDomain.EVENT_BUS) 30 | .addLabels(Label.REMOTE); 31 | } 32 | 33 | @Override 34 | protected Vertx vertx(TestContext context) { 35 | return Vertx.vertx(new VertxOptions().setPreferNativeTransport(true).setMetricsOptions(metricsOptions)) 36 | .exceptionHandler(context.exceptionHandler()); 37 | } 38 | 39 | @Test 40 | public void shouldWriteOnUnixSocket(TestContext ctx) { 41 | vertx = vertx(ctx); 42 | 43 | Async allDeployed = ctx.async(); 44 | vertx.deployVerticle( 45 | new DomainSocketServer()).onComplete( 46 | h -> vertx.deployVerticle(new DomainSocketClientTriggerVerticle()).onComplete( ch -> allDeployed.complete())); 47 | 48 | allDeployed.await(2000); 49 | waitForValue(ctx, "vertx.net.client.active.connections[remote=/var/tmp/myservice.sock]$VALUE", v -> v.intValue() == 0); 50 | List datapoints = listDatapoints(startsWith("vertx.net.client.")); 51 | assertThat(datapoints).contains( 52 | dp("vertx.net.client.active.connections[remote=/var/tmp/myservice.sock]$VALUE", 0), 53 | dp("vertx.net.client.bytes.written[remote=/var/tmp/myservice.sock]$COUNT", 4)); 54 | } 55 | 56 | public static class DomainSocketServer extends AbstractVerticle { 57 | @Override 58 | public void start(Promise startPromise) { 59 | vertx.createHttpServer().requestHandler(req -> { 60 | }) 61 | .listen(SocketAddress.domainSocketAddress("/var/tmp/myservice.sock")).onComplete(ar -> { 62 | if (ar.succeeded()) { 63 | startPromise.complete(); 64 | } else { 65 | startPromise.fail(ar.cause()); 66 | } 67 | }); 68 | } 69 | 70 | @Override 71 | public void stop(Promise stopPromise) { 72 | stopPromise.complete(); 73 | } 74 | } 75 | 76 | public static class DomainSocketClientTriggerVerticle extends AbstractVerticle { 77 | @Override 78 | public void start(Promise startPromise) { 79 | NetClient netClient = vertx.createNetClient(); 80 | SocketAddress addr = SocketAddress.domainSocketAddress("/var/tmp/myservice.sock"); 81 | netClient.connect(addr).onComplete(ar -> { 82 | if (ar.succeeded()) { 83 | NetSocket socket = ar.result().exceptionHandler(startPromise::fail); 84 | socket.write("test"); 85 | socket.close().onComplete(v -> startPromise.complete()); 86 | } else { 87 | startPromise.fail(ar.cause()); 88 | } 89 | }); 90 | } 91 | 92 | @Override 93 | public void stop(Promise stopPromise) { 94 | stopPromise.complete(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/impl/meters/TimersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests.impl.meters; 19 | 20 | import io.micrometer.core.instrument.MeterRegistry; 21 | import io.micrometer.core.instrument.Tags; 22 | import io.micrometer.core.instrument.Timer; 23 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 24 | import io.vertx.micrometer.Match; 25 | import io.vertx.micrometer.MatchType; 26 | import io.vertx.micrometer.backends.BackendRegistries; 27 | import org.junit.Test; 28 | 29 | import java.util.Collections; 30 | import java.util.concurrent.TimeUnit; 31 | 32 | import static io.vertx.micrometer.Label.EB_ADDRESS; 33 | import static org.assertj.core.api.Assertions.assertThat; 34 | 35 | /** 36 | * @author Joel Takvorian 37 | */ 38 | public class TimersTest { 39 | 40 | @Test 41 | public void shouldAliasTimerLabel() { 42 | MeterRegistry registry = new SimpleMeterRegistry(); 43 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 44 | .setLabel("address") 45 | .setType(MatchType.REGEX) 46 | .setValue("addr1") 47 | .setAlias("1"))); 48 | Timer t1 = Timer.builder("my_timer").tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 49 | t1.record(5, TimeUnit.MILLISECONDS); 50 | t1.record(8, TimeUnit.MILLISECONDS); 51 | Timer t2 = Timer.builder("my_timer").tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 52 | t2.record(10, TimeUnit.MILLISECONDS); 53 | 54 | Timer t = registry.find("my_timer").tags("address", "1").timer(); 55 | assertThat(t).isNotNull().extracting(Timer::count).containsExactly(2L); 56 | assertThat(t.totalTime(TimeUnit.MILLISECONDS)).isEqualTo(13); 57 | t = registry.find("my_timer").tags("address", "addr1").timer(); 58 | assertThat(t).isNull(); 59 | t = registry.find("my_timer").tags("address", "addr2").timer(); 60 | assertThat(t).isNotNull().extracting(Timer::count).containsExactly(1L); 61 | assertThat(t.totalTime(TimeUnit.MILLISECONDS)).isEqualTo(10); 62 | } 63 | 64 | @Test 65 | public void shouldIgnoreTimerLabel() { 66 | MeterRegistry registry = new SimpleMeterRegistry(); 67 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 68 | .setLabel("address") 69 | .setType(MatchType.REGEX) 70 | .setValue(".*") 71 | .setAlias("_"))); 72 | Timer t1 = Timer.builder("my_timer").tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 73 | t1.record(5, TimeUnit.MILLISECONDS); 74 | t1.record(8, TimeUnit.MILLISECONDS); 75 | Timer t2 = Timer.builder("my_timer").tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 76 | t2.record(10, TimeUnit.MILLISECONDS); 77 | 78 | Timer t = registry.find("my_timer").timer(); 79 | assertThat(t).isNotNull().extracting(Timer::count).containsExactly(3L); 80 | assertThat(t.totalTime(TimeUnit.MILLISECONDS)).isEqualTo(23); 81 | t = registry.find("my_timer").tags("address", "addr1").timer(); 82 | assertThat(t).isNull(); 83 | t = registry.find("my_timer").tags("address", "addr2").timer(); 84 | assertThat(t).isNull(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/Match.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer; 2 | 3 | import io.vertx.codegen.annotations.DataObject; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | /** 7 | * A match for a value. 8 | * 9 | * @author Julien Viet 10 | */ 11 | @DataObject 12 | public class Match { 13 | /** 14 | * The default value : {@link MatchType#EQUALS} 15 | */ 16 | public static final MatchType DEFAULT_TYPE = MatchType.EQUALS; 17 | 18 | private MetricsDomain domain; 19 | private String label; 20 | private String value; 21 | private MatchType type; 22 | private String alias; 23 | 24 | /** 25 | * Default constructor 26 | */ 27 | public Match() { 28 | type = DEFAULT_TYPE; 29 | } 30 | 31 | /** 32 | * Copy constructor 33 | * 34 | * @param other The other {@link Match} to copy when creating this 35 | */ 36 | public Match(Match other) { 37 | domain = other.domain; 38 | label = other.label; 39 | value = other.value; 40 | type = other.type; 41 | } 42 | 43 | /** 44 | * Create an instance from a {@link JsonObject} 45 | * 46 | * @param json the JsonObject to create it from 47 | */ 48 | public Match(JsonObject json) { 49 | if (json.containsKey("domain")) { 50 | domain = MetricsDomain.valueOf(json.getString("domain")); 51 | } 52 | label = json.getString("label"); 53 | value = json.getString("value"); 54 | type = MatchType.valueOf(json.getString("type", DEFAULT_TYPE.name())); 55 | alias = json.getString("alias"); 56 | } 57 | 58 | /** 59 | * @return the label domain 60 | */ 61 | public MetricsDomain getDomain() { 62 | return domain; 63 | } 64 | 65 | /** 66 | * Set the label domain, restricting this rule to a single domain. 67 | * 68 | * @param domain the label domain 69 | * @return a reference to this, so the API can be used fluently 70 | */ 71 | public Match setDomain(MetricsDomain domain) { 72 | this.domain = domain; 73 | return this; 74 | } 75 | 76 | /** 77 | * @return the label name 78 | */ 79 | public String getLabel() { 80 | return label; 81 | } 82 | 83 | /** 84 | * Set the label name. The match will apply to the values related to this key. 85 | * 86 | * @param label the label name 87 | * @return a reference to this, so the API can be used fluently 88 | */ 89 | public Match setLabel(String label) { 90 | this.label = label; 91 | return this; 92 | } 93 | 94 | /** 95 | * @return the matched value 96 | */ 97 | public String getValue() { 98 | return value; 99 | } 100 | 101 | /** 102 | * Set the matched value. 103 | * 104 | * @param value the value to match 105 | * @return a reference to this, so the API can be used fluently 106 | */ 107 | public Match setValue(String value) { 108 | this.value = value; 109 | return this; 110 | } 111 | 112 | /** 113 | * @return the matcher type 114 | */ 115 | public MatchType getType() { 116 | return type; 117 | } 118 | 119 | /** 120 | * Set the type of matching to apply. 121 | * 122 | * @param type the matcher type 123 | * @return a reference to this, so the API can be used fluently 124 | */ 125 | public Match setType(MatchType type) { 126 | this.type = type; 127 | return this; 128 | } 129 | 130 | /** 131 | * @return the matcher alias 132 | */ 133 | public String getAlias() { 134 | return alias; 135 | } 136 | 137 | /** 138 | * Set an alias that would replace the label value when it matches. 139 | * 140 | * @param alias the matcher alias 141 | * @return a reference to this, so the API can be used fluently 142 | */ 143 | public Match setAlias(String alias) { 144 | this.alias = alias; 145 | return this; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/impl/meters/SummariesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests.impl.meters; 19 | 20 | import io.micrometer.core.instrument.DistributionSummary; 21 | import io.micrometer.core.instrument.MeterRegistry; 22 | import io.micrometer.core.instrument.Tags; 23 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 24 | import io.vertx.micrometer.Match; 25 | import io.vertx.micrometer.MatchType; 26 | import io.vertx.micrometer.backends.BackendRegistries; 27 | import org.junit.Test; 28 | 29 | import java.util.Collections; 30 | 31 | import static io.vertx.micrometer.Label.EB_ADDRESS; 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | 34 | /** 35 | * @author Joel Takvorian 36 | */ 37 | public class SummariesTest { 38 | 39 | @Test 40 | public void shouldAliasSummaryLabel() { 41 | MeterRegistry registry = new SimpleMeterRegistry(); 42 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 43 | .setLabel("address") 44 | .setType(MatchType.REGEX) 45 | .setValue("addr1") 46 | .setAlias("1"))); 47 | DistributionSummary s1 = DistributionSummary.builder("my_summary").tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 48 | s1.record(5); 49 | s1.record(8); 50 | DistributionSummary s2 = DistributionSummary.builder("my_summary").tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 51 | s2.record(10); 52 | 53 | DistributionSummary s = registry.find("my_summary").tags("address", "1").summary(); 54 | assertThat(s).isNotNull().extracting(DistributionSummary::count).containsExactly(2L); 55 | assertThat(s.totalAmount()).isEqualTo(13); 56 | s = registry.find("my_summary").tags("address", "addr1").summary(); 57 | assertThat(s).isNull(); 58 | s = registry.find("my_summary").tags("address", "addr2").summary(); 59 | assertThat(s).isNotNull().extracting(DistributionSummary::count).containsExactly(1L); 60 | assertThat(s.totalAmount()).isEqualTo(10); 61 | } 62 | 63 | @Test 64 | public void shouldIgnoreSummaryLabel() { 65 | MeterRegistry registry = new SimpleMeterRegistry(); 66 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 67 | .setLabel("address") 68 | .setType(MatchType.REGEX) 69 | .setValue(".*") 70 | .setAlias("_"))); 71 | DistributionSummary s1 = DistributionSummary.builder("my_summary").tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 72 | s1.record(5); 73 | s1.record(8); 74 | DistributionSummary s2 = DistributionSummary.builder("my_summary").tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 75 | s2.record(10); 76 | 77 | DistributionSummary s = registry.find("my_summary").tags("address", "_").summary(); 78 | assertThat(s).isNotNull().extracting(DistributionSummary::count).containsExactly(3L); 79 | assertThat(s.totalAmount()).isEqualTo(23); 80 | s = registry.find("my_summary").tags("address", "addr1").summary(); 81 | assertThat(s).isNull(); 82 | s = registry.find("my_summary").tags("address", "addr2").summary(); 83 | assertThat(s).isNull(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/VertxPoolMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.impl; 18 | 19 | import io.micrometer.core.instrument.Counter; 20 | import io.micrometer.core.instrument.Tags; 21 | import io.micrometer.core.instrument.Timer; 22 | import io.micrometer.core.instrument.Timer.Sample; 23 | import io.vertx.core.spi.metrics.PoolMetrics; 24 | 25 | import java.util.concurrent.atomic.LongAdder; 26 | 27 | import static io.vertx.micrometer.Label.POOL_NAME; 28 | import static io.vertx.micrometer.Label.POOL_TYPE; 29 | import static io.vertx.micrometer.MetricsDomain.NAMED_POOLS; 30 | 31 | /** 32 | * @author Joel Takvorian 33 | */ 34 | class VertxPoolMetrics extends AbstractMetrics implements PoolMetrics { 35 | 36 | final Timer queueDelay; 37 | final LongAdder queueSize; 38 | final Timer usage; 39 | final LongAdder inUse; 40 | final LongAdder usageRatio; 41 | final Counter completed; 42 | 43 | VertxPoolMetrics(AbstractMetrics parent, String poolType, String poolName, int maxPoolSize) { 44 | super(parent, NAMED_POOLS); 45 | Tags tags = Tags.empty(); 46 | if (enabledLabels.contains(POOL_TYPE) || "http".equals(poolType)) { 47 | tags = tags.and(POOL_TYPE.toString(), poolType); 48 | } 49 | if (enabledLabels.contains(POOL_NAME) || "http".equals(poolType)) { 50 | tags = tags.and(POOL_NAME.toString(), poolName); 51 | } 52 | queueDelay = Timer.builder(names.getPoolQueueTime()) 53 | .description("Time spent in queue before being processed") 54 | .tags(tags) 55 | .register(registry); 56 | queueSize = longGaugeBuilder(names.getPoolQueuePending(), LongAdder::doubleValue) 57 | .description("Number of pending elements in queue") 58 | .tags(tags) 59 | .register(registry); 60 | usage = Timer.builder(names.getPoolUsage()) 61 | .description("Time using a resource") 62 | .tags(tags) 63 | .register(registry); 64 | inUse = longGaugeBuilder(names.getPoolInUse(), LongAdder::doubleValue) 65 | .description("Number of resources used") 66 | .tags(tags) 67 | .register(registry); 68 | usageRatio = longGaugeBuilder(names.getPoolUsageRatio(), value -> maxPoolSize > 0 ? value.doubleValue() / maxPoolSize : Double.NaN) 69 | .description("Pool usage ratio, only present if maximum pool size could be determined") 70 | .tags(tags) 71 | .register(registry); 72 | completed = Counter.builder(names.getPoolCompleted()) 73 | .description("Number of elements done with the resource") 74 | .tags(tags) 75 | .register(registry); 76 | } 77 | 78 | @Override 79 | public Sample enqueue() { 80 | queueSize.increment(); 81 | return Timer.start(); 82 | } 83 | 84 | @Override 85 | public void dequeue(Sample submitted) { 86 | queueSize.decrement(); 87 | submitted.stop(queueDelay); 88 | } 89 | 90 | @Override 91 | public Sample begin() { 92 | inUse.increment(); 93 | usageRatio.increment(); 94 | return Timer.start(); 95 | } 96 | 97 | @Override 98 | public void end(Sample timer) { 99 | inUse.decrement(); 100 | usageRatio.decrement(); 101 | timer.stop(usage); 102 | completed.increment(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/MatchersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests; 19 | 20 | import io.micrometer.core.instrument.Counter; 21 | import io.micrometer.core.instrument.MeterRegistry; 22 | import io.micrometer.core.instrument.Tags; 23 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 24 | import io.vertx.ext.unit.junit.VertxUnitRunner; 25 | import io.vertx.micrometer.Match; 26 | import io.vertx.micrometer.MatchType; 27 | import io.vertx.micrometer.MetricsDomain; 28 | import io.vertx.micrometer.backends.BackendRegistries; 29 | import org.junit.Test; 30 | import org.junit.runner.RunWith; 31 | 32 | import java.util.Collections; 33 | 34 | import static io.vertx.micrometer.Label.EB_ADDRESS; 35 | import static org.assertj.core.api.Assertions.assertThat; 36 | 37 | /** 38 | * @author Joel Takvorian 39 | */ 40 | @RunWith(VertxUnitRunner.class) 41 | public class MatchersTest { 42 | 43 | @Test 44 | public void shouldFilterMetric() { 45 | MeterRegistry registry = new SimpleMeterRegistry(); 46 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 47 | .setLabel("address") 48 | .setType(MatchType.EQUALS) 49 | .setValue("addr1"))); 50 | Counter c1 = Counter.builder("my_counter").tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 51 | c1.increment(); 52 | Counter c2 = Counter.builder("my_counter").tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 53 | c2.increment(); 54 | 55 | Counter c = registry.find("my_counter").tags("address", "addr1").counter(); 56 | assertThat(c).isNotNull().extracting(Counter::count).containsExactly(1d); 57 | c = registry.find("my_counter").tags("address", "addr2").counter(); 58 | assertThat(c).isNull(); 59 | } 60 | 61 | @Test 62 | public void shouldFilterDomainMetric() { 63 | MeterRegistry registry = new SimpleMeterRegistry(); 64 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 65 | .setLabel("address") 66 | .setDomain(MetricsDomain.EVENT_BUS) 67 | .setType(MatchType.EQUALS) 68 | .setValue("addr1"))); 69 | String metric1 = MetricsDomain.EVENT_BUS.getPrefix() + "_counter"; 70 | Counter c1 = Counter.builder(metric1).tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 71 | c1.increment(); 72 | Counter c2 = Counter.builder(metric1).tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 73 | c2.increment(); 74 | String metric2 = "another_domain_counter"; 75 | Counter c3 = Counter.builder(metric2).tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 76 | c3.increment(); 77 | Counter c4 = Counter.builder(metric2).tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 78 | c4.increment(); 79 | 80 | // In domain where the rule applies, filter is performed 81 | Counter c = registry.find(metric1).tags("address", "addr1").counter(); 82 | assertThat(c).isNotNull().extracting(Counter::count).containsExactly(1d); 83 | c = registry.find(metric1).tags("address", "addr2").counter(); 84 | assertThat(c).isNull(); 85 | // In other domain, no filter 86 | c = registry.find(metric2).tags("address", "addr1").counter(); 87 | assertThat(c).isNotNull().extracting(Counter::count).containsExactly(1d); 88 | c = registry.find(metric2).tags("address", "addr2").counter(); 89 | assertThat(c).isNotNull().extracting(Counter::count).containsExactly(1d); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/impl/meters/GaugesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests.impl.meters; 19 | 20 | import io.micrometer.core.instrument.Gauge; 21 | import io.micrometer.core.instrument.MeterRegistry; 22 | import io.micrometer.core.instrument.Tags; 23 | import io.micrometer.core.instrument.config.MeterFilter; 24 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 25 | import io.vertx.micrometer.Match; 26 | import io.vertx.micrometer.MatchType; 27 | import io.vertx.micrometer.backends.BackendRegistries; 28 | import io.vertx.micrometer.impl.meters.LongGauges; 29 | import org.junit.Test; 30 | 31 | import java.util.Collections; 32 | import java.util.concurrent.ConcurrentHashMap; 33 | import java.util.concurrent.atomic.LongAdder; 34 | 35 | import static io.vertx.micrometer.Label.EB_ADDRESS; 36 | import static org.assertj.core.api.Assertions.assertThat; 37 | 38 | /** 39 | * @author Joel Takvorian 40 | */ 41 | public class GaugesTest { 42 | 43 | private LongGauges longGauges = new LongGauges(new ConcurrentHashMap<>()); 44 | 45 | @Test 46 | public void shouldAliasGaugeLabel() { 47 | MeterRegistry registry = new SimpleMeterRegistry(); 48 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 49 | .setLabel("address") 50 | .setType(MatchType.REGEX) 51 | .setValue("addr1") 52 | .setAlias("1"))); 53 | LongAdder g1 = longGauges.builder("my_gauge", LongAdder::doubleValue).tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 54 | g1.increment(); 55 | g1.increment(); 56 | LongAdder g2 = longGauges.builder("my_gauge", LongAdder::doubleValue).tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 57 | g2.increment(); 58 | 59 | Gauge g = registry.get("my_gauge").tags("address", "1").gauge(); 60 | assertThat(g.value()).isEqualTo(2d); 61 | g = registry.find("my_gauge").tags("address", "addr1").gauge(); 62 | assertThat(g).isNull(); 63 | g = registry.get("my_gauge").tags("address", "addr2").gauge(); 64 | assertThat(g.value()).isEqualTo(1d); 65 | } 66 | 67 | @Test 68 | public void shouldIgnoreGaugeLabel() { 69 | MeterRegistry registry = new SimpleMeterRegistry(); 70 | BackendRegistries.registerMatchers(registry, Collections.singletonList(new Match() 71 | .setLabel("address") 72 | .setType(MatchType.REGEX) 73 | .setValue(".*") 74 | .setAlias("_"))); 75 | LongAdder g1 = longGauges.builder("my_gauge", LongAdder::doubleValue).tags(Tags.of(EB_ADDRESS.toString(), "addr1")).register(registry); 76 | g1.increment(); 77 | g1.increment(); 78 | LongAdder g2 = longGauges.builder("my_gauge", LongAdder::doubleValue).tags(Tags.of(EB_ADDRESS.toString(), "addr2")).register(registry); 79 | g2.increment(); 80 | 81 | Gauge g = registry.get("my_gauge").tags("address", "_").gauge(); 82 | assertThat(g.value()).isEqualTo(3d); 83 | g = registry.find("my_gauge").tags("address", "addr1").gauge(); 84 | assertThat(g).isNull(); 85 | g = registry.find("my_gauge").tags("address", "addr2").gauge(); 86 | assertThat(g).isNull(); 87 | } 88 | 89 | @Test 90 | public void shouldSupportNoopGauges() { 91 | MeterRegistry registry = new SimpleMeterRegistry(); 92 | registry.config().meterFilter(MeterFilter.deny(id -> "my_gauge".equals(id.getName()))); 93 | LongAdder g1 = longGauges.builder("my_gauge", LongAdder::doubleValue).register(registry); 94 | g1.increment(); 95 | 96 | assertThat(registry.find("my_gauge").gauges()).isEmpty(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/VertxDatagramNetClientNetServerSocketMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests; 19 | 20 | import io.vertx.core.datagram.DatagramSocket; 21 | import io.vertx.ext.unit.Async; 22 | import io.vertx.ext.unit.TestContext; 23 | import io.vertx.ext.unit.junit.VertxUnitRunner; 24 | import io.vertx.micrometer.Label; 25 | import io.vertx.micrometer.MetricsNaming; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | import java.util.List; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | 33 | /** 34 | * @author Joel Takvorian 35 | */ 36 | @RunWith(VertxUnitRunner.class) 37 | public class VertxDatagramNetClientNetServerSocketMetricsTest extends MicrometerMetricsTestBase { 38 | 39 | @Test 40 | public void shouldReportDatagramMetrics(TestContext context) { 41 | metricsOptions.addLabels(Label.LOCAL); 42 | 43 | vertx = vertx(context); 44 | 45 | String datagramContent = "some text"; 46 | int loops = 5; 47 | 48 | // Setup server 49 | int port = 9192; 50 | String host = "localhost"; 51 | Async receiveLatch = context.async(loops); 52 | Async listenLatch = context.async(); 53 | vertx.createDatagramSocket().listen(port, host).onComplete(context.asyncAssertSuccess(so -> { 54 | so.handler(packet -> receiveLatch.countDown()); 55 | listenLatch.complete(); 56 | })); 57 | listenLatch.awaitSuccess(15000); 58 | 59 | // Send to server 60 | DatagramSocket client = vertx.createDatagramSocket(); 61 | for (int i = 0; i < loops; i++) { 62 | client.send(datagramContent, port, host).onComplete(context.asyncAssertSuccess()); 63 | } 64 | receiveLatch.awaitSuccess(15000); 65 | 66 | waitForValue(context, "vertx.datagram.bytes.written[]$COUNT", value -> value.intValue() == 5); 67 | List datapoints = listDatapoints(startsWith("vertx.datagram.")); 68 | assertThat(datapoints).containsOnly( 69 | dp("vertx.datagram.bytes.written[]$COUNT", 5), 70 | dp("vertx.datagram.bytes.written[]$TOTAL", 45), // 45 = size("some text") * loops 71 | dp("vertx.datagram.bytes.read[local=localhost:9192]$COUNT", 5), 72 | dp("vertx.datagram.bytes.read[local=localhost:9192]$TOTAL", 45)); 73 | } 74 | 75 | @Test 76 | public void shouldReportInCompatibilityMode(TestContext context) { 77 | metricsOptions.setMetricsNaming(MetricsNaming.v3Names()); 78 | 79 | vertx = vertx(context); 80 | 81 | String datagramContent = "some text"; 82 | 83 | // Setup server 84 | int port = 9192; 85 | String host = "localhost"; 86 | Async receiveLatch = context.async(); 87 | Async listenLatch = context.async(); 88 | vertx.createDatagramSocket().listen(port, host).onComplete(context.asyncAssertSuccess(so -> { 89 | so.handler(packet -> receiveLatch.countDown()); 90 | listenLatch.complete(); 91 | })); 92 | listenLatch.awaitSuccess(15000); 93 | 94 | // Send to server 95 | DatagramSocket client = vertx.createDatagramSocket(); 96 | client.send(datagramContent, port, host).onComplete(context.asyncAssertSuccess()); 97 | receiveLatch.awaitSuccess(15000); 98 | 99 | waitForValue(context, "vertx.datagram.bytesSent[]$COUNT", value -> value.intValue() == 1); 100 | List datapoints = listDatapoints(startsWith("vertx.datagram.")); 101 | assertThat(datapoints).containsOnly( 102 | dp("vertx.datagram.bytesSent[]$COUNT", 1), 103 | dp("vertx.datagram.bytesSent[]$TOTAL", 9), 104 | dp("vertx.datagram.bytesReceived[]$COUNT", 1), 105 | dp("vertx.datagram.bytesReceived[]$TOTAL", 9)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/MicrometerMetricsFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.micrometer; 17 | 18 | import io.micrometer.core.instrument.Meter; 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | import io.vertx.core.VertxOptions; 21 | import io.vertx.core.json.JsonObject; 22 | import io.vertx.core.metrics.MetricsOptions; 23 | import io.vertx.core.spi.VertxMetricsFactory; 24 | import io.vertx.core.spi.metrics.VertxMetrics; 25 | import io.vertx.micrometer.backends.BackendRegistries; 26 | import io.vertx.micrometer.backends.BackendRegistry; 27 | import io.vertx.micrometer.impl.VertxMetricsImpl; 28 | import io.vertx.micrometer.impl.meters.LongGauges; 29 | 30 | import java.util.Map; 31 | import java.util.WeakHashMap; 32 | import java.util.concurrent.ConcurrentHashMap; 33 | import java.util.concurrent.ConcurrentMap; 34 | import java.util.concurrent.atomic.LongAdder; 35 | 36 | /** 37 | * The micrometer metrics registry. 38 | * 39 | * @author Joel Takvorian 40 | */ 41 | public class MicrometerMetricsFactory implements VertxMetricsFactory { 42 | 43 | private static final Map> longGaugesByRegistry = new WeakHashMap<>(1); 44 | 45 | private final MeterRegistry micrometerRegistry; 46 | 47 | public MicrometerMetricsFactory() { 48 | this(null); 49 | } 50 | 51 | /** 52 | * Build a factory passing the Micrometer MeterRegistry to be used by Vert.x. 53 | * 54 | * This is useful in several scenarios, such as: 55 | *
    56 | *
  • if there is already a MeterRegistry used in the application 57 | * that should be used by Vert.x as well.
  • 58 | *
  • to define some backend configuration that is not covered in this module 59 | * (example: reporting to non-covered backends such as New Relic)
  • 60 | *
  • to use Micrometer's CompositeRegistry
  • 61 | *
62 | * 63 | * This setter is mutually exclusive with setInfluxDbOptions/setPrometheusOptions/setJmxMetricsOptions 64 | * and takes precedence over them. 65 | * 66 | * @param micrometerRegistry the registry to use 67 | */ 68 | public MicrometerMetricsFactory(MeterRegistry micrometerRegistry) { 69 | this.micrometerRegistry = micrometerRegistry; 70 | } 71 | 72 | @Override 73 | public VertxMetrics metrics(VertxOptions vertxOptions) { 74 | MetricsOptions metricsOptions = vertxOptions.getMetricsOptions(); 75 | MicrometerMetricsOptions options; 76 | if (metricsOptions instanceof MicrometerMetricsOptions) { 77 | options = (MicrometerMetricsOptions) metricsOptions; 78 | } else { 79 | options = new MicrometerMetricsOptions(metricsOptions.toJson()); 80 | } 81 | BackendRegistry backendRegistry = BackendRegistries.setupBackend(options, micrometerRegistry); 82 | ConcurrentMap longGauges; 83 | synchronized (longGaugesByRegistry) { 84 | longGauges = longGaugesByRegistry.computeIfAbsent(backendRegistry.getMeterRegistry(), meterRegistry -> new ConcurrentHashMap<>()); 85 | } 86 | VertxMetricsImpl metrics = new VertxMetricsImpl(options, backendRegistry, new LongGauges(longGauges)); 87 | metrics.init(); 88 | 89 | return metrics; 90 | } 91 | 92 | @Override 93 | public MetricsOptions newOptions(MetricsOptions options) { 94 | if (options instanceof MicrometerMetricsOptions) { 95 | return new MicrometerMetricsOptions((MicrometerMetricsOptions) options); 96 | } else { 97 | return VertxMetricsFactory.super.newOptions(options); 98 | } 99 | } 100 | 101 | @Override 102 | public MetricsOptions newOptions() { 103 | return newOptions((JsonObject) null); 104 | } 105 | 106 | @Override 107 | public MetricsOptions newOptions(JsonObject jsonObject) { 108 | return jsonObject == null ? new MicrometerMetricsOptions() : new MicrometerMetricsOptions(jsonObject); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/VertxJmxMetricsOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer; 18 | 19 | import io.micrometer.jmx.JmxConfig; 20 | import io.vertx.codegen.annotations.DataObject; 21 | import io.vertx.codegen.json.annotations.JsonGen; 22 | import io.vertx.core.json.JsonObject; 23 | 24 | import java.time.Duration; 25 | 26 | /** 27 | * Options for Prometheus metrics backend. 28 | * 29 | * @author Joel Takvorian 30 | */ 31 | @DataObject 32 | @JsonGen(publicConverter = false, inheritConverter = true) 33 | public class VertxJmxMetricsOptions { 34 | 35 | /** 36 | * Default value for enabled = false. 37 | */ 38 | public static final boolean DEFAULT_ENABLED = false; 39 | 40 | /** 41 | * Default value for the domain = metrics. 42 | */ 43 | public static final String DEFAULT_DOMAIN = "metrics"; 44 | 45 | /** 46 | * Default value for metric collection interval (in seconds) = 10. 47 | */ 48 | public static final int DEFAULT_STEP = 10; 49 | 50 | private boolean enabled; 51 | private String domain; 52 | private int step; 53 | 54 | /** 55 | * Default constructor 56 | */ 57 | public VertxJmxMetricsOptions() { 58 | enabled = DEFAULT_ENABLED; 59 | domain = DEFAULT_DOMAIN; 60 | step = DEFAULT_STEP; 61 | } 62 | 63 | /** 64 | * Copy constructor 65 | * 66 | * @param other The other {@link VertxJmxMetricsOptions} to copy when creating this 67 | */ 68 | public VertxJmxMetricsOptions(VertxJmxMetricsOptions other) { 69 | enabled = other.enabled; 70 | domain = other.domain; 71 | step = other.step; 72 | } 73 | 74 | /** 75 | * Create an instance from a {@link JsonObject} 76 | * 77 | * @param json the JsonObject to create it from 78 | */ 79 | public VertxJmxMetricsOptions(JsonObject json) { 80 | this(); 81 | VertxJmxMetricsOptionsConverter.fromJson(json, this); 82 | } 83 | 84 | /** 85 | * @return a JSON representation of these options 86 | */ 87 | public JsonObject toJson() { 88 | JsonObject json = new JsonObject(); 89 | VertxJmxMetricsOptionsConverter.toJson(this, json); 90 | return json; 91 | } 92 | 93 | /** 94 | * Will JMX reporting be enabled? 95 | * 96 | * @return true if enabled, false if not. 97 | */ 98 | public boolean isEnabled() { 99 | return enabled; 100 | } 101 | 102 | /** 103 | * Set true to enable Prometheus reporting 104 | */ 105 | public VertxJmxMetricsOptions setEnabled(boolean enabled) { 106 | this.enabled = enabled; 107 | return this; 108 | } 109 | 110 | /** 111 | * Get the JMX domain under which metrics are published 112 | */ 113 | public String getDomain() { 114 | return domain; 115 | } 116 | 117 | /** 118 | * Set the JMX domain under which to publish metrics 119 | */ 120 | public VertxJmxMetricsOptions setDomain(String domain) { 121 | this.domain = domain; 122 | return this; 123 | } 124 | 125 | /** 126 | * Get the step of push intervals, in seconds 127 | */ 128 | public int getStep() { 129 | return step; 130 | } 131 | 132 | /** 133 | * Push interval steps, in seconds. Default is 10 seconds. 134 | */ 135 | public VertxJmxMetricsOptions setStep(int step) { 136 | this.step = step; 137 | return this; 138 | } 139 | 140 | /** 141 | * Convert these options to a Micrometer's {@code JmxConfig} object 142 | */ 143 | public JmxConfig toMicrometerConfig() { 144 | return new JmxConfig() { 145 | @Override 146 | public String get(String s) { 147 | return null; 148 | } 149 | 150 | @Override 151 | public String domain() { 152 | return domain; 153 | } 154 | 155 | @Override 156 | public Duration step() { 157 | return Duration.ofSeconds(step); 158 | } 159 | }; 160 | 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/VertxNetServerMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.micrometer.impl; 17 | 18 | import io.micrometer.core.instrument.Counter; 19 | import io.micrometer.core.instrument.Meter; 20 | import io.micrometer.core.instrument.Tags; 21 | import io.vertx.core.net.SocketAddress; 22 | import io.vertx.core.spi.metrics.TransportMetrics; 23 | import io.vertx.micrometer.MetricsDomain; 24 | import io.vertx.micrometer.impl.VertxNetServerMetrics.NetServerSocketMetric; 25 | import io.vertx.micrometer.impl.tags.Labels; 26 | 27 | import java.util.concurrent.atomic.LongAdder; 28 | 29 | import static io.vertx.micrometer.Label.*; 30 | import static io.vertx.micrometer.MetricsDomain.NET_SERVER; 31 | 32 | /** 33 | * @author Joel Takvorian 34 | */ 35 | class VertxNetServerMetrics extends AbstractMetrics implements TransportMetrics { 36 | 37 | final Tags local; 38 | private final Meter.MeterProvider netErrorCount; 39 | 40 | VertxNetServerMetrics(AbstractMetrics parent, SocketAddress localAddress) { 41 | this(parent, NET_SERVER, localAddress); 42 | } 43 | 44 | VertxNetServerMetrics(AbstractMetrics parent, MetricsDomain domain, SocketAddress localAddress) { 45 | super(parent, domain); 46 | if (enabledLabels.contains(LOCAL)) { 47 | local = Tags.of(LOCAL.toString(), Labels.address(localAddress)); 48 | } else { 49 | local = Tags.empty(); 50 | } 51 | netErrorCount = Counter.builder(names.getNetErrorCount()) 52 | .description("Number of errors") 53 | .withRegistry(registry); 54 | } 55 | 56 | @Override 57 | public String type() { 58 | return "tcp"; 59 | } 60 | 61 | @Override 62 | public NetServerSocketMetric connected(SocketAddress remoteAddress, String remoteName) { 63 | Tags tags = local; 64 | if (enabledLabels.contains(REMOTE)) { 65 | tags = tags.and(REMOTE.toString(), Labels.address(remoteAddress, remoteName)); 66 | } 67 | NetServerSocketMetric socketMetric = new NetServerSocketMetric(tags); 68 | socketMetric.connections.increment(); 69 | return socketMetric; 70 | } 71 | 72 | @Override 73 | public void disconnected(NetServerSocketMetric socketMetric, SocketAddress remoteAddress) { 74 | socketMetric.connections.decrement(); 75 | } 76 | 77 | @Override 78 | public void bytesRead(NetServerSocketMetric socketMetric, SocketAddress remoteAddress, long numberOfBytes) { 79 | socketMetric.bytesReceived.increment(numberOfBytes); 80 | } 81 | 82 | @Override 83 | public void bytesWritten(NetServerSocketMetric socketMetric, SocketAddress remoteAddress, long numberOfBytes) { 84 | socketMetric.bytesSent.increment(numberOfBytes); 85 | } 86 | 87 | @Override 88 | public void exceptionOccurred(NetServerSocketMetric socketMetric, SocketAddress remoteAddress, Throwable t) { 89 | Tags tags = socketMetric.tags; 90 | if (enabledLabels.contains(CLASS_NAME)) { 91 | tags = tags.and(CLASS_NAME.toString(), t.getClass().getSimpleName()); 92 | } 93 | netErrorCount.withTags(tags).increment(); 94 | } 95 | 96 | class NetServerSocketMetric { 97 | 98 | final Tags tags; 99 | 100 | final LongAdder connections; 101 | final Counter bytesReceived; 102 | final Counter bytesSent; 103 | 104 | NetServerSocketMetric(Tags tags) { 105 | this.tags = tags; 106 | connections = longGaugeBuilder(names.getNetActiveConnections(), LongAdder::doubleValue) 107 | .description("Number of opened connections to the server") 108 | .tags(tags) 109 | .register(registry); 110 | bytesReceived = Counter.builder(names.getNetBytesRead()) 111 | .description("Number of bytes received by the server") 112 | .tags(tags) 113 | .register(registry); 114 | bytesSent = Counter.builder(names.getNetBytesWritten()) 115 | .description("Number of bytes sent by the server") 116 | .tags(tags) 117 | .register(registry); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/VertxClientMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests; 19 | 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.internal.VertxInternal; 22 | import io.vertx.core.net.SocketAddress; 23 | import io.vertx.core.spi.metrics.ClientMetrics; 24 | import io.vertx.ext.unit.TestContext; 25 | import io.vertx.ext.unit.junit.VertxUnitRunner; 26 | import io.vertx.micrometer.Label; 27 | import io.vertx.micrometer.MicrometerMetricsOptions; 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | 31 | import java.util.List; 32 | import java.util.Stack; 33 | 34 | import static org.assertj.core.api.Assertions.assertThat; 35 | 36 | /** 37 | * @author Joel Takvorian 38 | */ 39 | @RunWith(VertxUnitRunner.class) 40 | public class VertxClientMetricsTest extends MicrometerMetricsTestBase { 41 | 42 | @Override 43 | protected MicrometerMetricsOptions metricOptions() { 44 | return super.metricOptions().addLabels(Label.REMOTE, Label.NAMESPACE); 45 | } 46 | 47 | @Test 48 | public void shouldReportProcessedClientMetrics(TestContext context) { 49 | vertx = vertx(context); 50 | 51 | FakeClient client = new FakeClient(vertx, "somewhere", "my namespace"); 52 | 53 | List datapoints = listDatapoints(startsWith("vertx.fake")); 54 | assertThat(datapoints).size().isEqualTo(5); 55 | 56 | client.process(6); 57 | datapoints = listDatapoints(startsWith("vertx.fake")); 58 | assertThat(datapoints).contains( 59 | dp("vertx.fake.processing.pending[client_namespace=my namespace,remote=somewhere]$VALUE", 6)); 60 | 61 | client.processed(2); 62 | datapoints = listDatapoints(startsWith("vertx.fake")); 63 | assertThat(datapoints).contains( 64 | dp("vertx.fake.processing.pending[client_namespace=my namespace,remote=somewhere]$VALUE", 4), 65 | dp("vertx.fake.processing.time[client_namespace=my namespace,remote=somewhere]$COUNT", 2)); 66 | 67 | client.reset(2); 68 | datapoints = listDatapoints(startsWith("vertx.fake")); 69 | assertThat(datapoints).contains( 70 | dp("vertx.fake.processing.pending[client_namespace=my namespace,remote=somewhere]$VALUE", 2), 71 | dp("vertx.fake.processing.time[client_namespace=my namespace,remote=somewhere]$COUNT", 4), 72 | dp("vertx.fake.resets[client_namespace=my namespace,remote=somewhere]$COUNT", 2)); 73 | } 74 | 75 | @Test 76 | public void shouldNotReportDisabledClientMetrics(TestContext context) { 77 | metricsOptions.addDisabledMetricsCategory("fake"); 78 | 79 | vertx = vertx(context); 80 | 81 | FakeClient client = new FakeClient(vertx, "somewhere", "my namespace"); 82 | List datapoints = listDatapoints(startsWith("vertx.fake")); 83 | assertThat(datapoints).isEmpty(); 84 | } 85 | 86 | static class FakeClient { 87 | final Vertx vertx; 88 | final ClientMetrics metrics; 89 | final Stack queue = new Stack<>(); 90 | final Stack processing = new Stack<>(); 91 | 92 | FakeClient(Vertx vertx, String where, String namespace) { 93 | this.vertx = vertx; 94 | metrics = ((VertxInternal)vertx).metrics().createClientMetrics( 95 | SocketAddress.domainSocketAddress(where), "fake", namespace); 96 | } 97 | 98 | void process(int quantity) { 99 | for (int i = 0; i < quantity; i++) { 100 | Object o = metrics.requestBegin("", ""); 101 | metrics.requestEnd(o); 102 | processing.push(o); 103 | } 104 | } 105 | 106 | void processed(int quantity) { 107 | for (int i = 0; i < quantity; i++) { 108 | Object o = processing.pop(); 109 | metrics.responseBegin(o, null); 110 | metrics.responseEnd(o); 111 | } 112 | } 113 | 114 | void reset(int quantity) { 115 | for (int i = 0; i < quantity; i++) { 116 | Object o = processing.pop(); 117 | metrics.requestReset(o); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/VertxHttpServerMetricsConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests; 19 | 20 | import io.micrometer.core.instrument.Tag; 21 | import io.vertx.core.AbstractVerticle; 22 | import io.vertx.core.Future; 23 | import io.vertx.core.Promise; 24 | import io.vertx.core.http.HttpClient; 25 | import io.vertx.core.http.HttpMethod; 26 | import io.vertx.core.http.HttpServer; 27 | import io.vertx.ext.unit.Async; 28 | import io.vertx.ext.unit.TestContext; 29 | import io.vertx.ext.unit.junit.VertxUnitRunner; 30 | import io.vertx.micrometer.MicrometerMetricsOptions; 31 | import org.junit.Test; 32 | import org.junit.runner.RunWith; 33 | 34 | import java.util.Collections; 35 | import java.util.List; 36 | 37 | import static org.assertj.core.api.Assertions.assertThat; 38 | 39 | /** 40 | * @author Joel Takvorian 41 | */ 42 | @RunWith(VertxUnitRunner.class) 43 | public class VertxHttpServerMetricsConfigTest extends MicrometerMetricsTestBase { 44 | 45 | private HttpServer httpServer; 46 | 47 | @Override 48 | protected MicrometerMetricsOptions metricOptions() { 49 | return super.metricOptions() 50 | .setServerRequestTagsProvider(req -> { 51 | String user = req.headers().get("user"); 52 | return Collections.singletonList(Tag.of("user", user)); 53 | }); 54 | } 55 | 56 | @Test 57 | public void shouldReportHttpServerMetricsWithCustomTags(TestContext ctx) { 58 | vertx = vertx(ctx); 59 | 60 | prepareServer(ctx); 61 | HttpClient client = vertx.createHttpClient(); 62 | sendRequest(ctx, client, "alice"); 63 | sendRequest(ctx, client, "bob"); 64 | 65 | waitForValue(ctx, "vertx.http.client.response.time[code=200,method=POST]$COUNT", 66 | value -> value.intValue() == 2); 67 | waitForValue(ctx, "vertx.http.client.active.requests[method=POST]$VALUE", 68 | value -> value.intValue() == 0); 69 | 70 | List datapoints = listDatapoints(startsWith("vertx.http.server.")); 71 | assertThat(datapoints).extracting(Datapoint::id).contains( 72 | "vertx.http.server.requests[code=200,method=POST,user=alice]$COUNT", 73 | "vertx.http.server.active.requests[method=POST,user=alice]$VALUE", 74 | "vertx.http.server.response.time[code=200,method=POST,user=alice]$COUNT", 75 | "vertx.http.server.requests[code=200,method=POST,user=bob]$COUNT", 76 | "vertx.http.server.active.requests[method=POST,user=bob]$VALUE", 77 | "vertx.http.server.response.time[code=200,method=POST,user=bob]$COUNT"); 78 | } 79 | 80 | private void prepareServer(TestContext ctx) { 81 | // Setup server 82 | Async serverReady = ctx.async(); 83 | vertx.deployVerticle(new AbstractVerticle() { 84 | @Override 85 | public void start(Promise startPromise) { 86 | httpServer = vertx.createHttpServer(); 87 | httpServer 88 | .requestHandler(req -> { 89 | vertx.setTimer(30L, handler -> 90 | req.response().setChunked(true).putHeader("Content-Type", "text/plain").end("")); 91 | }) 92 | .listen(9195, "127.0.0.1") 93 | .mapEmpty() 94 | .onComplete(startPromise); 95 | } 96 | }).onComplete(ctx.asyncAssertSuccess(v -> serverReady.complete())); 97 | serverReady.awaitSuccess(); 98 | } 99 | 100 | private void sendRequest(TestContext ctx, HttpClient client, String user) { 101 | Async async = ctx.async(); 102 | client.request(HttpMethod.POST, 9195, "127.0.0.1", "/") 103 | .compose(req -> req 104 | .putHeader("user", user) 105 | .send("") 106 | .compose(response -> { 107 | if (response.statusCode() != 200) { 108 | return Future.failedFuture(response.statusMessage()); 109 | } else { 110 | return response.body(); 111 | } 112 | })) 113 | .onComplete(ctx.asyncAssertSuccess(v -> async.countDown())); 114 | async.await(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/generated/io/vertx/micrometer/VertxInfluxDbOptionsConverter.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import io.vertx.core.json.JsonArray; 5 | 6 | /** 7 | * Converter and mapper for {@link io.vertx.micrometer.VertxInfluxDbOptions}. 8 | * NOTE: This class has been automatically generated from the {@link io.vertx.micrometer.VertxInfluxDbOptions} original class using Vert.x codegen. 9 | */ 10 | public class VertxInfluxDbOptionsConverter { 11 | 12 | static void fromJson(Iterable> json, VertxInfluxDbOptions obj) { 13 | for (java.util.Map.Entry member : json) { 14 | switch (member.getKey()) { 15 | case "enabled": 16 | if (member.getValue() instanceof Boolean) { 17 | obj.setEnabled((Boolean)member.getValue()); 18 | } 19 | break; 20 | case "uri": 21 | if (member.getValue() instanceof String) { 22 | obj.setUri((String)member.getValue()); 23 | } 24 | break; 25 | case "db": 26 | if (member.getValue() instanceof String) { 27 | obj.setDb((String)member.getValue()); 28 | } 29 | break; 30 | case "userName": 31 | if (member.getValue() instanceof String) { 32 | obj.setUserName((String)member.getValue()); 33 | } 34 | break; 35 | case "password": 36 | if (member.getValue() instanceof String) { 37 | obj.setPassword((String)member.getValue()); 38 | } 39 | break; 40 | case "retentionPolicy": 41 | if (member.getValue() instanceof String) { 42 | obj.setRetentionPolicy((String)member.getValue()); 43 | } 44 | break; 45 | case "compressed": 46 | if (member.getValue() instanceof Boolean) { 47 | obj.setCompressed((Boolean)member.getValue()); 48 | } 49 | break; 50 | case "step": 51 | if (member.getValue() instanceof Number) { 52 | obj.setStep(((Number)member.getValue()).intValue()); 53 | } 54 | break; 55 | case "connectTimeout": 56 | if (member.getValue() instanceof Number) { 57 | obj.setConnectTimeout(((Number)member.getValue()).intValue()); 58 | } 59 | break; 60 | case "readTimeout": 61 | if (member.getValue() instanceof Number) { 62 | obj.setReadTimeout(((Number)member.getValue()).intValue()); 63 | } 64 | break; 65 | case "batchSize": 66 | if (member.getValue() instanceof Number) { 67 | obj.setBatchSize(((Number)member.getValue()).intValue()); 68 | } 69 | break; 70 | case "org": 71 | if (member.getValue() instanceof String) { 72 | obj.setOrg((String)member.getValue()); 73 | } 74 | break; 75 | case "bucket": 76 | if (member.getValue() instanceof String) { 77 | obj.setBucket((String)member.getValue()); 78 | } 79 | break; 80 | case "token": 81 | if (member.getValue() instanceof String) { 82 | obj.setToken((String)member.getValue()); 83 | } 84 | break; 85 | } 86 | } 87 | } 88 | 89 | static void toJson(VertxInfluxDbOptions obj, JsonObject json) { 90 | toJson(obj, json.getMap()); 91 | } 92 | 93 | static void toJson(VertxInfluxDbOptions obj, java.util.Map json) { 94 | json.put("enabled", obj.isEnabled()); 95 | if (obj.getUri() != null) { 96 | json.put("uri", obj.getUri()); 97 | } 98 | if (obj.getDb() != null) { 99 | json.put("db", obj.getDb()); 100 | } 101 | if (obj.getUserName() != null) { 102 | json.put("userName", obj.getUserName()); 103 | } 104 | if (obj.getPassword() != null) { 105 | json.put("password", obj.getPassword()); 106 | } 107 | if (obj.getRetentionPolicy() != null) { 108 | json.put("retentionPolicy", obj.getRetentionPolicy()); 109 | } 110 | json.put("compressed", obj.isCompressed()); 111 | json.put("step", obj.getStep()); 112 | json.put("connectTimeout", obj.getConnectTimeout()); 113 | json.put("readTimeout", obj.getReadTimeout()); 114 | json.put("batchSize", obj.getBatchSize()); 115 | if (obj.getOrg() != null) { 116 | json.put("org", obj.getOrg()); 117 | } 118 | if (obj.getBucket() != null) { 119 | json.put("bucket", obj.getBucket()); 120 | } 121 | if (obj.getToken() != null) { 122 | json.put("token", obj.getToken()); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/VertxNetClientMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.impl; 18 | 19 | import io.micrometer.core.instrument.Counter; 20 | import io.micrometer.core.instrument.Meter.MeterProvider; 21 | import io.micrometer.core.instrument.Tags; 22 | import io.vertx.core.net.SocketAddress; 23 | import io.vertx.core.spi.metrics.TransportMetrics; 24 | import io.vertx.micrometer.MetricsDomain; 25 | import io.vertx.micrometer.impl.VertxNetClientMetrics.NetClientSocketMetric; 26 | import io.vertx.micrometer.impl.tags.Labels; 27 | 28 | import java.util.concurrent.atomic.LongAdder; 29 | 30 | import static io.vertx.micrometer.Label.*; 31 | import static io.vertx.micrometer.MetricsDomain.NET_CLIENT; 32 | 33 | /** 34 | * @author Joel Takvorian 35 | */ 36 | class VertxNetClientMetrics extends AbstractMetrics implements TransportMetrics { 37 | 38 | final Tags local; 39 | private final MeterProvider netErrorCount; 40 | 41 | VertxNetClientMetrics(AbstractMetrics parent, String metricsName, String localAddress) { 42 | this(parent, metricsName, NET_CLIENT, localAddress); 43 | } 44 | 45 | VertxNetClientMetrics(AbstractMetrics parent, String metricsName, MetricsDomain domain, String localAddress) { 46 | super(parent, domain); 47 | Tags base; 48 | if (enabledLabels.contains(CLIENT_NAME)) { 49 | base = Tags.of(CLIENT_NAME.toString(), metricsName == null ? "?" : metricsName); 50 | } else { 51 | base = Tags.empty(); 52 | } 53 | if (enabledLabels.contains(LOCAL)) { 54 | local = base.and(LOCAL.toString(), localAddress == null ? "?" : localAddress); 55 | } else { 56 | local = base; 57 | } 58 | netErrorCount = Counter.builder(names.getNetErrorCount()) 59 | .description("Number of errors") 60 | .withRegistry(registry); 61 | } 62 | 63 | @Override 64 | public String type() { 65 | return "tcp"; 66 | } 67 | 68 | @Override 69 | public NetClientSocketMetric connected(SocketAddress remoteAddress, String remoteName) { 70 | Tags tags = local; 71 | if (enabledLabels.contains(REMOTE)) { 72 | tags = tags.and(REMOTE.toString(), Labels.address(remoteAddress, remoteName)); 73 | } 74 | NetClientSocketMetric socketMetric = new NetClientSocketMetric(tags); 75 | socketMetric.connections.increment(); 76 | return socketMetric; 77 | } 78 | 79 | @Override 80 | public void disconnected(NetClientSocketMetric socketMetric, SocketAddress remoteAddress) { 81 | socketMetric.connections.decrement(); 82 | } 83 | 84 | @Override 85 | public void bytesRead(NetClientSocketMetric socketMetric, SocketAddress remoteAddress, long numberOfBytes) { 86 | socketMetric.bytesReceived.increment(numberOfBytes); 87 | } 88 | 89 | @Override 90 | public void bytesWritten(NetClientSocketMetric socketMetric, SocketAddress remoteAddress, long numberOfBytes) { 91 | socketMetric.bytesSent.increment(numberOfBytes); 92 | } 93 | 94 | @Override 95 | public void exceptionOccurred(NetClientSocketMetric socketMetric, SocketAddress remoteAddress, Throwable t) { 96 | Tags tags = socketMetric.tags; 97 | if (enabledLabels.contains(CLASS_NAME)) { 98 | tags = tags.and(CLASS_NAME.toString(), t.getClass().getSimpleName()); 99 | } 100 | netErrorCount.withTags(tags).increment(); 101 | } 102 | 103 | class NetClientSocketMetric { 104 | 105 | final Tags tags; 106 | 107 | final LongAdder connections; 108 | final Counter bytesReceived; 109 | final Counter bytesSent; 110 | 111 | NetClientSocketMetric(Tags tags) { 112 | this.tags = tags; 113 | connections = longGaugeBuilder(names.getNetActiveConnections(), LongAdder::doubleValue) 114 | .description("Number of connections to the remote host currently opened") 115 | .tags(tags) 116 | .register(registry); 117 | bytesReceived = Counter.builder(names.getNetBytesRead()) 118 | .description("Number of bytes received from the remote host") 119 | .tags(tags) 120 | .register(registry); 121 | bytesSent = Counter.builder(names.getNetBytesWritten()) 122 | .description("Number of bytes sent to the remote host") 123 | .tags(tags) 124 | .register(registry); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/VertxPoolMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests; 19 | 20 | import io.vertx.core.WorkerExecutor; 21 | import io.vertx.ext.unit.Async; 22 | import io.vertx.ext.unit.TestContext; 23 | import io.vertx.ext.unit.junit.VertxUnitRunner; 24 | import org.assertj.core.util.DoubleComparator; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | 28 | import java.util.Comparator; 29 | import java.util.List; 30 | import java.util.concurrent.CountDownLatch; 31 | 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | 34 | /** 35 | * @author Joel Takvorian 36 | */ 37 | @RunWith(VertxUnitRunner.class) 38 | public class VertxPoolMetricsTest extends MicrometerMetricsTestBase { 39 | 40 | @Test 41 | public void shouldReportNamedPoolMetrics(TestContext context) { 42 | vertx = vertx(context); 43 | 44 | int maxPoolSize = 8; 45 | int taskCount = maxPoolSize * 3; 46 | int sleepMillis = 30; 47 | 48 | // Setup executor 49 | WorkerExecutor workerExecutor = vertx.createSharedWorkerExecutor("test-worker", maxPoolSize); 50 | Async ready = context.async(taskCount); 51 | for (int i = 0; i < taskCount; i++) { 52 | workerExecutor.executeBlocking(() -> { 53 | Thread.sleep(sleepMillis); 54 | return null; 55 | }, false).onComplete(context.asyncAssertSuccess(v -> { 56 | ready.countDown(); 57 | })); 58 | } 59 | ready.awaitSuccess(); 60 | waitForValue( 61 | context, 62 | "vertx.pool.completed[pool_name=test-worker,pool_type=worker]$COUNT", 63 | value -> value.intValue() == taskCount); 64 | 65 | List datapoints = listDatapoints(startsWith("vertx.pool").and(hasTag("pool_name", "test-worker"))); 66 | assertThat(datapoints).hasSize(10).contains( 67 | dp("vertx.pool.queue.pending[pool_name=test-worker,pool_type=worker]$VALUE", 0), 68 | dp("vertx.pool.in.use[pool_name=test-worker,pool_type=worker]$VALUE", 0), 69 | dp("vertx.pool.ratio[pool_name=test-worker,pool_type=worker]$VALUE", 0), 70 | dp("vertx.pool.completed[pool_name=test-worker,pool_type=worker]$COUNT", taskCount), 71 | dp("vertx.pool.queue.time[pool_name=test-worker,pool_type=worker]$COUNT", taskCount), 72 | dp("vertx.pool.usage[pool_name=test-worker,pool_type=worker]$COUNT", taskCount)); 73 | 74 | assertThat(datapoints) 75 | .usingFieldByFieldElementComparator() 76 | .usingComparatorForElementFieldsWithType(new DoubleComparator(0.1), Double.class) 77 | .contains(dp("vertx.pool.usage[pool_name=test-worker,pool_type=worker]$MAX", sleepMillis / 1000d)); 78 | 79 | class GreaterOrEqualsComparator implements Comparator { 80 | @Override 81 | public int compare(Double o1, Double o2) { 82 | return o1 < o2 ? -1 : 0; 83 | } 84 | } 85 | 86 | assertThat(datapoints) 87 | .usingFieldByFieldElementComparator() 88 | .usingComparatorForElementFieldsWithType(new GreaterOrEqualsComparator(), Double.class) 89 | .contains( 90 | dp("vertx.pool.usage[pool_name=test-worker,pool_type=worker]$TOTAL_TIME", taskCount * sleepMillis / 1000d)); 91 | } 92 | 93 | @Test 94 | public void shouldReportUsageMetrics(TestContext context) { 95 | vertx = vertx(context); 96 | 97 | int maxPoolSize = 8; 98 | int taskCount = maxPoolSize * 3; 99 | CountDownLatch latch = new CountDownLatch(1); 100 | 101 | Async ready = context.async(taskCount); 102 | WorkerExecutor workerExecutor = vertx.createSharedWorkerExecutor("test-worker", maxPoolSize); 103 | for (int i = 0; i < taskCount; i++) { 104 | workerExecutor.executeBlocking(() -> { 105 | latch.await(); 106 | return null; 107 | }, false).onComplete(context.asyncAssertSuccess(v -> { 108 | ready.countDown(); 109 | })); 110 | } 111 | 112 | waitForValue( 113 | context, 114 | "vertx.pool.in.use[pool_name=test-worker,pool_type=worker]$VALUE", 115 | value -> value.intValue() == maxPoolSize); 116 | 117 | List datapoints = listDatapoints(startsWith("vertx.pool.ratio").and(hasTag("pool_name", "test-worker"))); 118 | assertThat(datapoints).hasSize(1).contains( 119 | dp("vertx.pool.ratio[pool_name=test-worker,pool_type=worker]$VALUE", 1.0D)); 120 | 121 | latch.countDown(); 122 | ready.awaitSuccess(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/backend/PrometheusTestHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.tests.backend; 18 | 19 | import io.vertx.core.Future; 20 | import io.vertx.core.Handler; 21 | import io.vertx.core.Vertx; 22 | import io.vertx.core.buffer.Buffer; 23 | import io.vertx.core.http.HttpClientResponse; 24 | import io.vertx.core.http.HttpMethod; 25 | import io.vertx.core.parsetools.RecordParser; 26 | import io.vertx.ext.unit.Async; 27 | import io.vertx.ext.unit.TestContext; 28 | 29 | import java.util.Collections; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | 33 | /** 34 | * @author Joel Takvorian 35 | */ 36 | public final class PrometheusTestHelper { 37 | private static final int DEFAULT_MAX_ATTEMPS = 10; 38 | private static final long DEFAULT_SLEEP_BEFORE_RETRY_MS = 100; 39 | 40 | private PrometheusTestHelper() { 41 | } 42 | 43 | public static void tryConnect(Vertx vertx, TestContext context, int port, String host, String requestURI, Handler bodyReader) { 44 | tryConnect(vertx, context, port, host, requestURI, bodyReader, DEFAULT_MAX_ATTEMPS, DEFAULT_SLEEP_BEFORE_RETRY_MS); 45 | } 46 | 47 | public static void tryConnect(Vertx vertx, 48 | TestContext context, 49 | int port, 50 | String host, 51 | String requestURI, 52 | Handler bodyReader, 53 | int maxAttempts, 54 | long sleepBeforeRetryMs) { 55 | tryConnect(vertx, context, port, host, requestURI, res -> { 56 | context.assertEquals(200, res.statusCode()); 57 | res.bodyHandler(bodyReader); 58 | }, maxAttempts, sleepBeforeRetryMs, 0); 59 | } 60 | 61 | public static Set getMetricNames(Vertx vertx, TestContext context, int port, String host, String requestURI, long timeout) { 62 | return getMetricNames(vertx, context, port, host, requestURI, timeout, DEFAULT_MAX_ATTEMPS, DEFAULT_SLEEP_BEFORE_RETRY_MS); 63 | } 64 | 65 | public static Set getMetricNames(Vertx vertx, 66 | TestContext context, 67 | int port, 68 | String host, 69 | String requestURI, 70 | long timeout, 71 | int maxAttempts, 72 | long sleepBeforeRetryMs) { 73 | Async async = context.async(); 74 | Set metrics = Collections.synchronizedSet(new HashSet<>()); 75 | tryConnect(vertx, context, port, host, requestURI, resp -> { 76 | context.assertEquals(200, resp.statusCode()); 77 | RecordParser parser = RecordParser.newDelimited("\n", resp); 78 | parser.exceptionHandler(context::fail).endHandler(v -> { 79 | async.countDown(); 80 | }).handler(buffer -> { 81 | String line = buffer.toString(); 82 | if (line.startsWith("# TYPE")) { 83 | metrics.add(line.split(" ")[2]); 84 | } 85 | }); 86 | }, maxAttempts, sleepBeforeRetryMs, 0); 87 | async.await(timeout); 88 | return metrics; 89 | } 90 | 91 | private static void tryConnect(Vertx vertx, 92 | TestContext context, 93 | int port, 94 | String host, 95 | String requestURI, 96 | Handler respHandler, 97 | int maxAttempts, 98 | long sleepBeforeRetryMs, 99 | int attempt) { 100 | vertx.createHttpClient().request(HttpMethod.GET, port, host, requestURI).compose(req -> 101 | req.send().compose(resp -> { 102 | respHandler.handle(resp); 103 | return Future.succeededFuture(); 104 | }) 105 | ).onFailure(cause -> { 106 | if (attempt < maxAttempts) { 107 | vertx.setTimer(sleepBeforeRetryMs, l -> { 108 | tryConnect(vertx, context, port, host, requestURI, respHandler, maxAttempts, sleepBeforeRetryMs, attempt + 1); 109 | }); 110 | } else { 111 | context.fail(cause); 112 | } 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/VertxNetClientServerMetricsTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer.tests; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.Promise; 5 | import io.vertx.core.net.NetClient; 6 | import io.vertx.core.net.NetClientOptions; 7 | import io.vertx.core.net.NetServer; 8 | import io.vertx.core.net.NetSocket; 9 | import io.vertx.ext.unit.Async; 10 | import io.vertx.ext.unit.TestContext; 11 | import io.vertx.ext.unit.junit.VertxUnitRunner; 12 | import io.vertx.micrometer.*; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | import java.util.List; 17 | import java.util.concurrent.ForkJoinPool; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | /** 22 | * @author Joel Takvorian 23 | */ 24 | @RunWith(VertxUnitRunner.class) 25 | public class VertxNetClientServerMetricsTest extends MicrometerMetricsTestBase { 26 | private static final int SENT_COUNT = 68; 27 | private static final String SERVER_RESPONSE = "some text"; 28 | private static final String CLIENT_REQUEST = "pitchounette"; 29 | 30 | private final int concurrentClients = ForkJoinPool.commonPool().getParallelism(); 31 | private NetServer netServer; 32 | 33 | @Override 34 | protected MicrometerMetricsOptions metricOptions() { 35 | return super.metricOptions() 36 | .addDisabledMetricsCategory(MetricsDomain.EVENT_BUS) 37 | .addLabels(Label.LOCAL, Label.REMOTE, Label.CLIENT_NAME) 38 | .addLabelMatch(new Match() 39 | .setDomain(MetricsDomain.NET_SERVER) 40 | .setType(MatchType.REGEX) 41 | .setLabel("remote") 42 | .setValue(".*") 43 | .setAlias("_")); 44 | } 45 | 46 | @Override 47 | protected void setUp(TestContext ctx) { 48 | super.setUp(ctx); 49 | 50 | vertx = vertx(ctx); 51 | 52 | // Setup server 53 | Async serverReady = ctx.async(); 54 | vertx.deployVerticle(new AbstractVerticle() { 55 | @Override 56 | public void start(Promise startPromise) { 57 | netServer = vertx.createNetServer(); 58 | netServer 59 | .connectHandler(socket -> socket.handler(buffer -> socket.write(SERVER_RESPONSE))) 60 | .listen(9194, "localhost") 61 | .mapEmpty() 62 | .onComplete(startPromise); 63 | } 64 | }).onComplete(ctx.asyncAssertSuccess(v -> serverReady.complete())); 65 | serverReady.awaitSuccess(); 66 | } 67 | 68 | @Test 69 | public void shouldReportNetClientMetrics(TestContext ctx) { 70 | runClientRequests(ctx); 71 | 72 | waitForValue(ctx, "vertx.net.client.bytes.read[client_name=my_client_name,local=?,remote=localhost:9194]$COUNT", 73 | value -> value.intValue() == concurrentClients * SENT_COUNT * SERVER_RESPONSE.getBytes().length); 74 | 75 | List datapoints = listDatapoints(startsWith("vertx.net.client.")); 76 | assertThat(datapoints).containsOnly( 77 | dp("vertx.net.client.active.connections[client_name=my_client_name,local=?,remote=localhost:9194]$VALUE", 0), 78 | dp("vertx.net.client.bytes.read[client_name=my_client_name,local=?,remote=localhost:9194]$COUNT", concurrentClients * SENT_COUNT * SERVER_RESPONSE.getBytes().length), 79 | dp("vertx.net.client.bytes.written[client_name=my_client_name,local=?,remote=localhost:9194]$COUNT", concurrentClients * SENT_COUNT * CLIENT_REQUEST.getBytes().length)); 80 | } 81 | 82 | @Test 83 | public void shouldReportNetServerMetrics(TestContext ctx) { 84 | runClientRequests(ctx); 85 | 86 | waitForValue(ctx, "vertx.net.server.bytes.read[local=localhost:9194,remote=_]$COUNT", 87 | value -> value.intValue() == concurrentClients * SENT_COUNT * CLIENT_REQUEST.getBytes().length); 88 | 89 | List datapoints = listDatapoints(startsWith("vertx.net.server.")); 90 | assertThat(datapoints).containsOnly( 91 | dp("vertx.net.server.active.connections[local=localhost:9194,remote=_]$VALUE", 0), 92 | dp("vertx.net.server.bytes.read[local=localhost:9194,remote=_]$COUNT", concurrentClients * SENT_COUNT * CLIENT_REQUEST.getBytes().length), 93 | dp("vertx.net.server.bytes.written[local=localhost:9194,remote=_]$COUNT", concurrentClients * SENT_COUNT * SERVER_RESPONSE.getBytes().length)); 94 | } 95 | 96 | private void runClientRequests(TestContext ctx) { 97 | Async clientsFinished = ctx.async(concurrentClients); 98 | for (int i = 0; i < concurrentClients; i++) { 99 | ForkJoinPool.commonPool().execute(() -> { 100 | NetClient client = vertx.createNetClient(new NetClientOptions().setMetricsName("my_client_name")); 101 | request(client, ctx); 102 | clientsFinished.countDown(); 103 | }); 104 | } 105 | clientsFinished.awaitSuccess(); 106 | } 107 | 108 | private void request(NetClient client, TestContext ctx) { 109 | for (int i = 0; i < SENT_COUNT; i++) { 110 | Async async = ctx.async(); 111 | client.connect(9194, "localhost").onComplete(res -> { 112 | if (res.failed()) { 113 | async.complete(); 114 | ctx.fail(res.cause()); 115 | return; 116 | } 117 | NetSocket socket = res.result().exceptionHandler(t -> { 118 | async.complete(); 119 | ctx.fail(t); 120 | }); 121 | socket.handler(buf -> socket.close()); 122 | socket.write(CLIENT_REQUEST); 123 | socket.closeHandler(v -> async.complete()); 124 | }); 125 | async.await(); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/backend/CustomMicrometerMetricsITest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2017 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.tests.backend; 18 | 19 | import io.micrometer.core.instrument.Clock; 20 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 21 | import io.micrometer.influx.InfluxConfig; 22 | import io.micrometer.influx.InfluxMeterRegistry; 23 | import io.micrometer.jmx.JmxMeterRegistry; 24 | import io.micrometer.prometheusmetrics.PrometheusConfig; 25 | import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; 26 | import io.vertx.core.Vertx; 27 | import io.vertx.core.http.HttpServerOptions; 28 | import io.vertx.ext.unit.Async; 29 | import io.vertx.ext.unit.TestContext; 30 | import io.vertx.ext.unit.junit.VertxUnitRunner; 31 | import io.vertx.micrometer.*; 32 | import io.vertx.micrometer.tests.MicrometerMetricsTestBase; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | 36 | import javax.management.MBeanServer; 37 | import javax.management.ObjectName; 38 | import java.lang.management.ManagementFactory; 39 | import java.time.Duration; 40 | import java.util.Hashtable; 41 | 42 | import static org.assertj.core.api.Assertions.assertThat; 43 | 44 | @RunWith(VertxUnitRunner.class) 45 | public class CustomMicrometerMetricsITest extends MicrometerMetricsTestBase { 46 | 47 | private Vertx vertxForSimulatedServer = Vertx.vertx(); 48 | 49 | @Override 50 | protected void tearDown(TestContext context) { 51 | super.tearDown(context); 52 | vertxForSimulatedServer.close().onComplete(context.asyncAssertSuccess()); 53 | } 54 | 55 | @Test 56 | public void shouldReportWithCompositeRegistry(TestContext context) throws Exception { 57 | // Mock an influxdb server 58 | Async asyncInflux = context.async(); 59 | InfluxDbTestHelper.simulateInfluxServer(vertxForSimulatedServer, context, 8087, body -> { 60 | try { 61 | context.verify(w -> assertThat(body) 62 | .contains("vertx_eventbus_handlers,address=test-eb,metric_type=gauge value=1")); 63 | } finally { 64 | asyncInflux.complete(); 65 | } 66 | }); 67 | 68 | CompositeMeterRegistry myRegistry = new CompositeMeterRegistry(); 69 | myRegistry.add(new JmxMeterRegistry(s -> null, Clock.SYSTEM)); 70 | myRegistry.add(new InfluxMeterRegistry(new InfluxConfig() { 71 | @Override 72 | public String get(String s) { 73 | return null; 74 | } 75 | @Override 76 | public Duration step() { 77 | return Duration.ofSeconds(1); 78 | } 79 | 80 | @Override 81 | public String uri() { 82 | return "http://localhost:8087"; 83 | } 84 | 85 | @Override 86 | public boolean autoCreateDb() { 87 | return false; 88 | } 89 | }, Clock.SYSTEM)); 90 | 91 | meterRegistry = myRegistry; 92 | 93 | metricsOptions = new MicrometerMetricsOptions() 94 | .setRegistryName(registryName) 95 | .addDisabledMetricsCategory(MetricsDomain.HTTP_SERVER) 96 | .addDisabledMetricsCategory(MetricsDomain.NAMED_POOLS) 97 | .addLabels(Label.EB_ADDRESS) 98 | .setEnabled(true); 99 | 100 | vertx = vertx(context); 101 | 102 | // Send something on the eventbus and wait til it's received 103 | Async asyncEB = context.async(); 104 | vertx.eventBus().consumer("test-eb", msg -> asyncEB.complete()); 105 | vertx.eventBus().publish("test-eb", "test message"); 106 | asyncEB.await(2000); 107 | 108 | // Read MBean 109 | MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 110 | assertThat(mbs.getDomains()).contains("metrics"); 111 | Hashtable table = new Hashtable<>(); 112 | table.put("type", "gauges"); 113 | table.put("name", "vertxEventbusHandlers.address.test-eb"); 114 | Number result = (Number) mbs.getAttribute(new ObjectName("metrics", table), "Value"); 115 | assertThat(result).isEqualTo(1d); 116 | 117 | // Await influx 118 | asyncInflux.awaitSuccess(); 119 | } 120 | 121 | @Test 122 | public void shouldPublishQuantilesWithProvidedRegistry(TestContext context) throws Exception { 123 | PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); 124 | 125 | meterRegistry = registry; 126 | metricsOptions = new MicrometerMetricsOptions() 127 | .setPrometheusOptions(new VertxPrometheusOptions().setEnabled(true) 128 | .setPublishQuantiles(true) 129 | .setStartEmbeddedServer(true) 130 | .setEmbeddedServerOptions(new HttpServerOptions().setPort(9090))) 131 | .setEnabled(true); 132 | 133 | vertx = vertx(context); 134 | 135 | Async async = context.async(); 136 | // Dummy connection to trigger some metrics 137 | PrometheusTestHelper.tryConnect(vertx, context, 9090, "localhost", "/metrics", r1 -> { 138 | // Delay to make "sure" metrics are populated 139 | vertx.setTimer(500, l -> { 140 | assertThat(registry.scrape()).contains("vertx_http_client_response_time_seconds_bucket{code=\"200\""); 141 | async.complete(); 142 | }); 143 | }); 144 | async.awaitSuccess(10000); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/MetricsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer.impl; 2 | 3 | import io.micrometer.core.instrument.Timer; 4 | import io.micrometer.core.instrument.*; 5 | import io.micrometer.core.instrument.distribution.HistogramSnapshot; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.core.metrics.Measured; 8 | import io.vertx.core.spi.metrics.Metrics; 9 | import io.vertx.core.spi.metrics.MetricsProvider; 10 | import io.vertx.micrometer.MetricsService; 11 | 12 | import java.util.*; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.Stream; 16 | 17 | /** 18 | * @author Julien Viet 19 | * @author Joel Takvorian 20 | */ 21 | public class MetricsServiceImpl implements MetricsService { 22 | 23 | private final MicrometerMetrics metrics; 24 | 25 | public MetricsServiceImpl(Measured measured) { 26 | MetricsProvider provider = (MetricsProvider) measured; 27 | Metrics baseMetrics = provider.getMetrics(); 28 | if (baseMetrics instanceof MicrometerMetrics) { 29 | metrics = (MicrometerMetrics) baseMetrics; 30 | } else { 31 | metrics = null; 32 | } 33 | } 34 | 35 | @Override 36 | public String getBaseName() { 37 | return metrics != null ? metrics.baseName() : null; 38 | } 39 | 40 | @Override 41 | public JsonObject getMetricsSnapshot() { 42 | return metrics != null ? snapshot(metrics.registry(), metrics.baseName()) : null; 43 | } 44 | 45 | @Override 46 | public JsonObject getMetricsSnapshot(String baseName) { 47 | return metrics != null ? snapshot(metrics.registry(), baseName) : null; 48 | } 49 | 50 | @Override 51 | public Set metricsNames() { 52 | if (metrics != null) { 53 | return metrics.registry().getMeters().stream() 54 | .map(m -> m.getId().getName()) 55 | .collect(Collectors.toSet()); 56 | } 57 | return Collections.emptySet(); 58 | } 59 | 60 | /** 61 | * @return the Micrometer registry used for these metrics, null if no metrics is available. 62 | */ 63 | public MeterRegistry getRegistry() { 64 | return metrics != null ? metrics.registry() : null; 65 | } 66 | 67 | private JsonObject snapshot(MeterRegistry registry, String baseName) { 68 | Stream filtered; 69 | if (baseName == null) { 70 | filtered = registry.getMeters().stream(); 71 | } else { 72 | filtered = registry.getMeters().stream() 73 | .filter(m -> m.getId().getName().startsWith(baseName)); 74 | } 75 | LinkedHashMap> map = filtered 76 | .sorted(Comparator.comparing(m -> m.getId().getName())) 77 | .collect( 78 | Collectors.groupingBy(m -> m.getId().getName(), 79 | LinkedHashMap::new, 80 | Collectors.mapping(MetricsServiceImpl::metricToJson, Collectors.toList()))); 81 | return new JsonObject((Map) (Object) map); 82 | } 83 | 84 | private static JsonObject metricToJson(Meter meter) { 85 | JsonObject tags = new JsonObject(); 86 | meter.getId().getTags().forEach(tag -> tags.put(tag.getKey(), tag.getValue())); 87 | JsonObject obj = new JsonObject().put("tags", tags); 88 | return meter.match(gauge -> gaugeToJson(obj, gauge), 89 | counter -> counterToJson(obj, counter), 90 | timer -> timerToJson(obj, timer), 91 | summary -> summaryToJson(obj, summary), 92 | longTaskTimer -> longTaskTimerToJson(obj, longTaskTimer), 93 | timeGauge -> timeGaugeToJson(obj, timeGauge), 94 | functionCounter -> functionCounterToJson(obj, functionCounter), 95 | functionTimer -> functionTimerToJson(obj, functionTimer), 96 | m -> obj.put("type", "unknown")); 97 | } 98 | 99 | private static JsonObject summaryToJson(JsonObject obj, DistributionSummary summary) { 100 | HistogramSnapshot snapshot = summary.takeSnapshot(false); 101 | return obj.put("type", "summary") 102 | .put("count", snapshot.count()) 103 | .put("total", snapshot.total()) 104 | .put("mean", snapshot.mean()) 105 | .put("max", snapshot.max()); 106 | } 107 | 108 | private static JsonObject timerToJson(JsonObject obj, Timer timer) { 109 | return obj.put("type", "timer") 110 | .put("count", timer.count()) 111 | .put("totalTimeMs", timer.totalTime(TimeUnit.MILLISECONDS)) 112 | .put("meanMs", timer.mean(TimeUnit.MILLISECONDS)) 113 | .put("maxMs", timer.max(TimeUnit.MILLISECONDS)); 114 | } 115 | 116 | private static JsonObject gaugeToJson(JsonObject obj, Gauge gauge) { 117 | return obj.put("type", "gauge") 118 | .put("value", gauge.value()); 119 | } 120 | 121 | private static JsonObject counterToJson(JsonObject obj, Counter counter) { 122 | return obj.put("type", "counter") 123 | .put("count", counter.count()); 124 | } 125 | 126 | private static JsonObject longTaskTimerToJson(JsonObject obj, LongTaskTimer longTaskTimer) { 127 | return obj.put("type", "longTaskTimer") 128 | .put("activeTasks", longTaskTimer.activeTasks()) 129 | .put("durationMs", longTaskTimer.duration(TimeUnit.MILLISECONDS)); 130 | } 131 | 132 | private static JsonObject timeGaugeToJson(JsonObject obj, TimeGauge timeGauge) { 133 | return obj.put("type", "timeGauge") 134 | .put("valueMs", timeGauge.value(TimeUnit.MILLISECONDS)); 135 | } 136 | 137 | private static JsonObject functionCounterToJson(JsonObject obj, FunctionCounter functionCounter) { 138 | return obj.put("type", "functionCounter") 139 | .put("count", functionCounter.count()); 140 | } 141 | 142 | private static JsonObject functionTimerToJson(JsonObject obj, FunctionTimer functionTimer) { 143 | return obj.put("type", "functionTimer") 144 | .put("count", functionTimer.count()) 145 | .put("totalTimeMs", functionTimer.totalTime(TimeUnit.MILLISECONDS)) 146 | .put("meanMs", functionTimer.mean(TimeUnit.MILLISECONDS)); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/ExternalConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.micrometer.tests; 18 | 19 | import io.vertx.core.Launcher; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.http.HttpServerOptions; 22 | import io.vertx.core.json.JsonObject; 23 | import io.vertx.ext.unit.Async; 24 | import io.vertx.ext.unit.TestContext; 25 | import io.vertx.ext.unit.junit.VertxUnitRunner; 26 | import io.vertx.micrometer.MicrometerMetricsOptions; 27 | import io.vertx.micrometer.VertxPrometheusOptions; 28 | import io.vertx.micrometer.tests.backend.PrometheusTestHelper; 29 | import org.junit.After; 30 | import org.junit.Rule; 31 | import org.junit.Test; 32 | import org.junit.rules.TemporaryFolder; 33 | import org.junit.runner.RunWith; 34 | 35 | import java.io.File; 36 | import java.nio.file.Files; 37 | import java.util.Set; 38 | 39 | import static org.assertj.core.api.Assertions.assertThat; 40 | 41 | /** 42 | * @author Thomas Segismont 43 | */ 44 | @RunWith(VertxUnitRunner.class) 45 | public class ExternalConfigurationTest { 46 | 47 | @Rule 48 | public TemporaryFolder temporaryFolder = new TemporaryFolder(); 49 | 50 | private volatile Vertx vertx; 51 | 52 | @After 53 | public void tearDown(TestContext context) { 54 | if (vertx != null) { 55 | vertx.close().onComplete(context.asyncAssertSuccess()); 56 | } 57 | } 58 | 59 | @Test 60 | public void testPrometheusDefaults(TestContext context) throws Exception { 61 | VertxPrometheusOptions prometheusOptions = new VertxPrometheusOptions() 62 | .setEnabled(true) 63 | .setStartEmbeddedServer(true) 64 | .setEmbeddedServerOptions(new HttpServerOptions().setPort(9999)); 65 | MicrometerMetricsOptions metricsOptions = new MicrometerMetricsOptions() 66 | .setEnabled(true) 67 | .setPrometheusOptions(prometheusOptions); 68 | 69 | startVertx(context, metricsOptions); 70 | 71 | Set metrics = PrometheusTestHelper.getMetricNames(vertx, context, 9999, "localhost", "/metrics", 3000); 72 | assertThat(metrics).contains("vertx_http_client_active_connections") 73 | .doesNotContain("jvm_classes_loaded"); 74 | } 75 | 76 | @Test 77 | public void testJvmMetricsEnabled(TestContext context) throws Exception { 78 | VertxPrometheusOptions prometheusOptions = new VertxPrometheusOptions() 79 | .setEnabled(true) 80 | .setStartEmbeddedServer(true) 81 | .setEmbeddedServerOptions(new HttpServerOptions().setPort(9999)); 82 | MicrometerMetricsOptions metricsOptions = new MicrometerMetricsOptions() 83 | .setEnabled(true) 84 | .setJvmMetricsEnabled(true) 85 | .setPrometheusOptions(prometheusOptions); 86 | 87 | startVertx(context, metricsOptions); 88 | 89 | Set metrics = PrometheusTestHelper.getMetricNames(vertx, context, 9999, "localhost", "/metrics", 3000); 90 | assertThat(metrics).contains( 91 | "jvm_classes_loaded_classes", // from ClassLoaderMetrics 92 | "jvm_compilation_time_ms_total", // from JvmCompilationMetrics 93 | "jvm_gc_memory_promoted_bytes_total", // from JvmGcMetrics 94 | "jvm_gc_overhead", // from JvmHeapPressureMetrics 95 | "jvm_info", // from JvmInfoMetrics 96 | "jvm_buffer_count_buffers", // from JvmMemoryMetrics 97 | "jvm_threads_live_threads", // from JvmThreadMetrics 98 | "system_cpu_count", // from ProcessorMetrics 99 | "process_uptime_seconds" // from UptimeMetrics 100 | ); 101 | } 102 | 103 | @Test 104 | public void testNettyMetricsEnabled(TestContext context) throws Exception { 105 | VertxPrometheusOptions prometheusOptions = new VertxPrometheusOptions() 106 | .setEnabled(true) 107 | .setStartEmbeddedServer(true) 108 | .setEmbeddedServerOptions(new HttpServerOptions().setPort(9999)); 109 | MicrometerMetricsOptions metricsOptions = new MicrometerMetricsOptions() 110 | .setEnabled(true) 111 | .setNettyMetricsEnabled(true) 112 | .setPrometheusOptions(prometheusOptions); 113 | 114 | startVertx(context, metricsOptions); 115 | 116 | Set metrics = PrometheusTestHelper.getMetricNames(vertx, context, 9999, "localhost", "/metrics", 3000); 117 | assertThat(metrics).contains( 118 | "netty_allocator_pooled_arenas", // from NettyAllocatorMetrics 119 | "netty_eventexecutor_tasks_pending" // from NettyEventExecutorMetrics 120 | ); 121 | } 122 | 123 | private void startVertx(TestContext context, MicrometerMetricsOptions metricsOptions) throws Exception { 124 | JsonObject json = new JsonObject() 125 | .put("metricsOptions", metricsOptions.toJson()); 126 | 127 | File optionsFile = temporaryFolder.newFile(); 128 | Files.write(optionsFile.toPath(), json.toBuffer().getBytes()); 129 | 130 | MyLauncher myLauncher = new MyLauncher(context); 131 | myLauncher.dispatch(new String[]{"run", "java:" + MyVerticle.class.getName(), "-options", optionsFile.getPath()}); 132 | myLauncher.await(); 133 | } 134 | 135 | private class MyLauncher extends Launcher { 136 | 137 | Async startAsync; 138 | 139 | MyLauncher(TestContext context) { 140 | startAsync = context.async(); 141 | } 142 | 143 | @Override 144 | public void afterStartingVertx(Vertx vertx) { 145 | ExternalConfigurationTest.this.vertx = vertx; 146 | startAsync.countDown(); 147 | } 148 | 149 | void await() { 150 | startAsync.await(3000); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/generated/io/vertx/micrometer/MicrometerMetricsOptionsConverter.java: -------------------------------------------------------------------------------- 1 | package io.vertx.micrometer; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import io.vertx.core.json.JsonArray; 5 | 6 | /** 7 | * Converter and mapper for {@link io.vertx.micrometer.MicrometerMetricsOptions}. 8 | * NOTE: This class has been automatically generated from the {@link io.vertx.micrometer.MicrometerMetricsOptions} original class using Vert.x codegen. 9 | */ 10 | public class MicrometerMetricsOptionsConverter { 11 | 12 | static void fromJson(Iterable> json, MicrometerMetricsOptions obj) { 13 | for (java.util.Map.Entry member : json) { 14 | switch (member.getKey()) { 15 | case "enabled": 16 | if (member.getValue() instanceof Boolean) { 17 | obj.setEnabled((Boolean)member.getValue()); 18 | } 19 | break; 20 | case "disabledMetricsCategories": 21 | if (member.getValue() instanceof JsonArray) { 22 | java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); 23 | ((Iterable)member.getValue()).forEach( item -> { 24 | if (item instanceof String) 25 | list.add((String)item); 26 | }); 27 | obj.setDisabledMetricsCategories(list); 28 | } 29 | break; 30 | case "registryName": 31 | if (member.getValue() instanceof String) { 32 | obj.setRegistryName((String)member.getValue()); 33 | } 34 | break; 35 | case "labels": 36 | if (member.getValue() instanceof JsonArray) { 37 | java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); 38 | ((Iterable)member.getValue()).forEach( item -> { 39 | if (item instanceof String) 40 | list.add(io.vertx.micrometer.Label.valueOf((String)item)); 41 | }); 42 | obj.setLabels(list); 43 | } 44 | break; 45 | case "labelMatches": 46 | if (member.getValue() instanceof JsonArray) { 47 | java.util.ArrayList list = new java.util.ArrayList<>(); 48 | ((Iterable)member.getValue()).forEach( item -> { 49 | if (item instanceof JsonObject) 50 | list.add(new io.vertx.micrometer.Match((io.vertx.core.json.JsonObject)item)); 51 | }); 52 | obj.setLabelMatches(list); 53 | } 54 | break; 55 | case "labelMatchs": 56 | if (member.getValue() instanceof JsonArray) { 57 | ((Iterable)member.getValue()).forEach( item -> { 58 | if (item instanceof JsonObject) 59 | obj.addLabelMatch(new io.vertx.micrometer.Match((io.vertx.core.json.JsonObject)item)); 60 | }); 61 | } 62 | break; 63 | case "influxDbOptions": 64 | if (member.getValue() instanceof JsonObject) { 65 | obj.setInfluxDbOptions(new io.vertx.micrometer.VertxInfluxDbOptions((io.vertx.core.json.JsonObject)member.getValue())); 66 | } 67 | break; 68 | case "prometheusOptions": 69 | if (member.getValue() instanceof JsonObject) { 70 | obj.setPrometheusOptions(new io.vertx.micrometer.VertxPrometheusOptions((io.vertx.core.json.JsonObject)member.getValue())); 71 | } 72 | break; 73 | case "jmxMetricsOptions": 74 | if (member.getValue() instanceof JsonObject) { 75 | obj.setJmxMetricsOptions(new io.vertx.micrometer.VertxJmxMetricsOptions((io.vertx.core.json.JsonObject)member.getValue())); 76 | } 77 | break; 78 | case "jvmMetricsEnabled": 79 | if (member.getValue() instanceof Boolean) { 80 | obj.setJvmMetricsEnabled((Boolean)member.getValue()); 81 | } 82 | break; 83 | case "nettyMetricsEnabled": 84 | if (member.getValue() instanceof Boolean) { 85 | obj.setNettyMetricsEnabled((Boolean)member.getValue()); 86 | } 87 | break; 88 | case "metricsNaming": 89 | if (member.getValue() instanceof JsonObject) { 90 | obj.setMetricsNaming(new io.vertx.micrometer.MetricsNaming((io.vertx.core.json.JsonObject)member.getValue())); 91 | } 92 | break; 93 | case "meterCacheEnabled": 94 | if (member.getValue() instanceof Boolean) { 95 | obj.setMeterCacheEnabled((Boolean)member.getValue()); 96 | } 97 | break; 98 | } 99 | } 100 | } 101 | 102 | static void toJson(MicrometerMetricsOptions obj, JsonObject json) { 103 | toJson(obj, json.getMap()); 104 | } 105 | 106 | static void toJson(MicrometerMetricsOptions obj, java.util.Map json) { 107 | json.put("enabled", obj.isEnabled()); 108 | if (obj.getDisabledMetricsCategories() != null) { 109 | JsonArray array = new JsonArray(); 110 | obj.getDisabledMetricsCategories().forEach(item -> array.add(item)); 111 | json.put("disabledMetricsCategories", array); 112 | } 113 | if (obj.getRegistryName() != null) { 114 | json.put("registryName", obj.getRegistryName()); 115 | } 116 | if (obj.getLabels() != null) { 117 | JsonArray array = new JsonArray(); 118 | obj.getLabels().forEach(item -> array.add(item.name())); 119 | json.put("labels", array); 120 | } 121 | if (obj.getInfluxDbOptions() != null) { 122 | json.put("influxDbOptions", obj.getInfluxDbOptions().toJson()); 123 | } 124 | if (obj.getPrometheusOptions() != null) { 125 | json.put("prometheusOptions", obj.getPrometheusOptions().toJson()); 126 | } 127 | if (obj.getJmxMetricsOptions() != null) { 128 | json.put("jmxMetricsOptions", obj.getJmxMetricsOptions().toJson()); 129 | } 130 | json.put("jvmMetricsEnabled", obj.isJvmMetricsEnabled()); 131 | json.put("nettyMetricsEnabled", obj.isNettyMetricsEnabled()); 132 | if (obj.getMetricsNaming() != null) { 133 | json.put("metricsNaming", obj.getMetricsNaming().toJson()); 134 | } 135 | json.put("meterCacheEnabled", obj.isMeterCacheEnabled()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/VertxPrometheusOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer; 18 | 19 | import io.vertx.codegen.annotations.DataObject; 20 | import io.vertx.codegen.json.annotations.JsonGen; 21 | import io.vertx.core.http.HttpServerOptions; 22 | import io.vertx.core.json.JsonObject; 23 | 24 | /** 25 | * Options for Prometheus metrics backend. 26 | * 27 | * @author Joel Takvorian 28 | */ 29 | @DataObject 30 | @JsonGen(publicConverter = false, inheritConverter = true) 31 | public class VertxPrometheusOptions { 32 | 33 | /** 34 | * Default value for enabled = false. 35 | */ 36 | public static final boolean DEFAULT_ENABLED = false; 37 | 38 | /** 39 | * Default value for starting an embedded server = false. 40 | */ 41 | public static final boolean DEFAULT_START_EMBEDDED_SERVER = false; 42 | 43 | /** 44 | * The default metrics endpoint = /metrics when using an embedded server. 45 | */ 46 | public static final String DEFAULT_EMBEDDED_SERVER_ENDPOINT = "/metrics"; 47 | 48 | /** 49 | * Default value for publishing histogram quantiles = false. 50 | */ 51 | public static final boolean DEFAULT_PUBLISH_QUANTILES = false; 52 | 53 | private boolean enabled; 54 | private boolean startEmbeddedServer; 55 | private HttpServerOptions embeddedServerOptions; 56 | private String embeddedServerEndpoint; 57 | private boolean publishQuantiles; 58 | 59 | /** 60 | * Default constructor 61 | */ 62 | public VertxPrometheusOptions() { 63 | enabled = DEFAULT_ENABLED; 64 | startEmbeddedServer = DEFAULT_START_EMBEDDED_SERVER; 65 | embeddedServerEndpoint = DEFAULT_EMBEDDED_SERVER_ENDPOINT; 66 | publishQuantiles = DEFAULT_PUBLISH_QUANTILES; 67 | } 68 | 69 | /** 70 | * Copy constructor 71 | * 72 | * @param other The other {@link VertxPrometheusOptions} to copy when creating this 73 | */ 74 | public VertxPrometheusOptions(VertxPrometheusOptions other) { 75 | enabled = other.enabled; 76 | startEmbeddedServer = other.startEmbeddedServer; 77 | embeddedServerEndpoint = other.embeddedServerEndpoint != null ? other.embeddedServerEndpoint : DEFAULT_EMBEDDED_SERVER_ENDPOINT; 78 | if (other.embeddedServerOptions != null) { 79 | embeddedServerOptions = new HttpServerOptions(other.embeddedServerOptions); 80 | } 81 | publishQuantiles = other.publishQuantiles; 82 | } 83 | 84 | /** 85 | * Create an instance from a {@link io.vertx.core.json.JsonObject} 86 | * 87 | * @param json the JsonObject to create it from 88 | */ 89 | public VertxPrometheusOptions(JsonObject json) { 90 | this(); 91 | VertxPrometheusOptionsConverter.fromJson(json, this); 92 | } 93 | 94 | 95 | /** 96 | * @return a JSON representation of these options 97 | */ 98 | public JsonObject toJson() { 99 | JsonObject json = new JsonObject(); 100 | VertxPrometheusOptionsConverter.toJson(this, json); 101 | return json; 102 | } 103 | 104 | /** 105 | * Will Prometheus reporting be enabled? 106 | * 107 | * @return true if enabled, false if not. 108 | */ 109 | public boolean isEnabled() { 110 | return enabled; 111 | } 112 | 113 | /** 114 | * Set true to enable Prometheus reporting 115 | */ 116 | public VertxPrometheusOptions setEnabled(boolean enabled) { 117 | this.enabled = enabled; 118 | return this; 119 | } 120 | 121 | /** 122 | * Returns true if it is configured to init an embedded web server to expose Prometheus metrics 123 | */ 124 | public boolean isStartEmbeddedServer() { 125 | return startEmbeddedServer; 126 | } 127 | 128 | /** 129 | * When true, an embedded server will init to expose metrics with Prometheus format. 130 | */ 131 | public VertxPrometheusOptions setStartEmbeddedServer(boolean startEmbeddedServer) { 132 | this.startEmbeddedServer = startEmbeddedServer; 133 | return this; 134 | } 135 | 136 | /** 137 | * Get the HTTP server options of the embedded server, if any 138 | */ 139 | public HttpServerOptions getEmbeddedServerOptions() { 140 | return embeddedServerOptions; 141 | } 142 | 143 | /** 144 | * HTTP server options for the embedded server 145 | * @param embeddedServerOptions the server options 146 | */ 147 | public VertxPrometheusOptions setEmbeddedServerOptions(HttpServerOptions embeddedServerOptions) { 148 | this.embeddedServerOptions = embeddedServerOptions; 149 | return this; 150 | } 151 | 152 | /** 153 | * Set metrics endpoint. Use conjointly with the embedded server options. Defaults to /metrics. 154 | * @param embeddedServerEndpoint metrics endpoint 155 | */ 156 | public VertxPrometheusOptions setEmbeddedServerEndpoint(String embeddedServerEndpoint) { 157 | this.embeddedServerEndpoint = embeddedServerEndpoint; 158 | return this; 159 | } 160 | 161 | /** 162 | * Get the HTTP endpoint used if an embedded server is configured 163 | */ 164 | public String getEmbeddedServerEndpoint() { 165 | return embeddedServerEndpoint; 166 | } 167 | 168 | /** 169 | * @return true if quantile stats are published 170 | */ 171 | public boolean isPublishQuantiles() { 172 | return publishQuantiles; 173 | } 174 | 175 | /** 176 | * Set true to publish histogram stats, necessary to compute quantiles. 177 | * Note that it generates many new timeseries for stats, which is why it is deactivated by default. 178 | * 179 | * @param publishQuantiles the publishing quantiles flag 180 | * @return a reference to this, so the API can be used fluently 181 | */ 182 | public VertxPrometheusOptions setPublishQuantiles(boolean publishQuantiles) { 183 | this.publishQuantiles = publishQuantiles; 184 | return this; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/MicrometerMetricsTestBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests; 19 | 20 | 21 | import io.micrometer.core.instrument.Meter; 22 | import io.micrometer.core.instrument.MeterRegistry; 23 | import io.micrometer.core.instrument.Tag; 24 | import io.vertx.core.Vertx; 25 | import io.vertx.core.VertxBuilder; 26 | import io.vertx.core.VertxOptions; 27 | import io.vertx.ext.unit.Async; 28 | import io.vertx.ext.unit.TestContext; 29 | import io.vertx.micrometer.MicrometerMetricsFactory; 30 | import io.vertx.micrometer.MicrometerMetricsOptions; 31 | import io.vertx.micrometer.VertxPrometheusOptions; 32 | import io.vertx.micrometer.backends.BackendRegistries; 33 | import org.junit.After; 34 | import org.junit.Before; 35 | 36 | import java.util.ArrayList; 37 | import java.util.List; 38 | import java.util.Objects; 39 | import java.util.UUID; 40 | import java.util.function.Predicate; 41 | import java.util.stream.Collectors; 42 | 43 | public class MicrometerMetricsTestBase { 44 | 45 | public static Predicate ALL = a -> true; 46 | 47 | protected String registryName; 48 | protected MicrometerMetricsOptions metricsOptions; 49 | protected MeterRegistry meterRegistry; 50 | protected Vertx vertx; 51 | 52 | @Before 53 | public void before(TestContext context) { 54 | setUp(context); 55 | } 56 | 57 | protected void setUp(TestContext context) { 58 | registryName = UUID.randomUUID().toString(); 59 | metricsOptions = metricOptions(); 60 | } 61 | 62 | protected MeterRegistry meterRegistry() { 63 | return meterRegistry; 64 | } 65 | 66 | protected MicrometerMetricsOptions metricOptions() { 67 | return new MicrometerMetricsOptions() 68 | .setPrometheusOptions(new VertxPrometheusOptions().setEnabled(true)) 69 | .setRegistryName(registryName) 70 | .setEnabled(true); 71 | } 72 | 73 | protected Vertx vertx(TestContext context) { 74 | MeterRegistry meterRegistry = meterRegistry(); 75 | VertxBuilder builder = Vertx 76 | .builder() 77 | .with(new VertxOptions() 78 | .setMetricsOptions(metricsOptions)); 79 | if (meterRegistry != null) { 80 | builder.withMetrics(new MicrometerMetricsFactory(meterRegistry)); 81 | } 82 | return builder 83 | .build() 84 | .exceptionHandler(context.exceptionHandler()); 85 | } 86 | 87 | @After 88 | public void after(TestContext context) { 89 | tearDown(context); 90 | } 91 | 92 | protected void tearDown(TestContext context) { 93 | if (vertx != null) { 94 | vertx.close().onComplete(context.asyncAssertSuccess()); 95 | } 96 | } 97 | 98 | public void waitForValue(TestContext context, String fullName, Predicate p) { 99 | Async ready = context.async(); 100 | vertx.setPeriodic(200, id -> { 101 | try { 102 | listDatapoints(m -> true).stream() 103 | .filter(dp -> fullName.equals(dp.id())) 104 | .filter(dp -> p.test(dp.value())) 105 | .findAny() 106 | .ifPresent(dp -> { 107 | vertx.cancelTimer(id); 108 | ready.complete(); 109 | }); 110 | } catch (NoRegistryException e) { 111 | context.fail(e); 112 | vertx.cancelTimer(id); 113 | } 114 | }); 115 | ready.awaitSuccess(10000); 116 | } 117 | 118 | public List listDatapoints(Predicate predicate) { 119 | List result = new ArrayList<>(); 120 | MeterRegistry registry = BackendRegistries.getNow(registryName); 121 | if (registry == null) { 122 | throw new NoRegistryException(registryName); 123 | } 124 | registry.forEachMeter(m -> { 125 | if (predicate.test(m)) { 126 | String id = id(m); 127 | m.measure().forEach(measurement -> { 128 | result.add(new Datapoint(id + "$" + measurement.getStatistic().name(), measurement.getValue())); 129 | }); 130 | } 131 | }); 132 | return result; 133 | } 134 | 135 | private static String id(Meter m) { 136 | return m.getId().getName() + "[" 137 | + m.getId().getTags().stream() 138 | .map(t -> t.getKey() + '=' + t.getValue()) 139 | .collect(Collectors.joining(",")) 140 | + "]"; 141 | } 142 | 143 | public static Predicate startsWith(String start) { 144 | return m -> m.getId().getName().startsWith(start); 145 | } 146 | 147 | public static Predicate hasTag(String key, String value) { 148 | return m -> m.getId().getTags().contains(Tag.of(key, value)); 149 | } 150 | 151 | public static Datapoint dp(String id, double value) { 152 | return new Datapoint(id, value); 153 | } 154 | 155 | public static Datapoint dp(String id, int value) { 156 | return new Datapoint(id, (double) value); 157 | } 158 | 159 | public static class Datapoint { 160 | private final String id; 161 | private final Double value; 162 | 163 | private Datapoint(String id, Double value) { 164 | this.id = id; 165 | this.value = value; 166 | } 167 | 168 | String id() { 169 | return id; 170 | } 171 | 172 | Double value() { 173 | return value; 174 | } 175 | 176 | @Override 177 | public boolean equals(Object o) { 178 | if (this == o) return true; 179 | if (o == null || getClass() != o.getClass()) return false; 180 | Datapoint datapoint = (Datapoint) o; 181 | return Objects.equals(id, datapoint.id) && 182 | Objects.equals(value, datapoint.value); 183 | } 184 | 185 | @Override 186 | public int hashCode() { 187 | return Objects.hash(id, value); 188 | } 189 | 190 | @Override 191 | public String toString() { 192 | return id + "/" + value; 193 | } 194 | } 195 | 196 | public static class NoRegistryException extends RuntimeException { 197 | public NoRegistryException(String regName) { 198 | super("Registry '" + regName + "' not found"); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/micrometer/tests/VertxEventBusMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.vertx.micrometer.tests; 19 | 20 | import io.vertx.core.AbstractVerticle; 21 | import io.vertx.core.DeploymentOptions; 22 | import io.vertx.core.Promise; 23 | import io.vertx.core.eventbus.EventBus; 24 | import io.vertx.core.eventbus.MessageConsumer; 25 | import io.vertx.core.eventbus.MessageConsumerOptions; 26 | import io.vertx.core.json.JsonObject; 27 | import io.vertx.ext.unit.Async; 28 | import io.vertx.ext.unit.TestContext; 29 | import io.vertx.ext.unit.junit.VertxUnitRunner; 30 | import io.vertx.micrometer.Label; 31 | import org.junit.Test; 32 | import org.junit.runner.RunWith; 33 | 34 | import java.util.List; 35 | 36 | import static org.assertj.core.api.Assertions.assertThat; 37 | import static org.assertj.core.api.Fail.fail; 38 | 39 | /** 40 | * @author Joel Takvorian 41 | */ 42 | @RunWith(VertxUnitRunner.class) 43 | public class VertxEventBusMetricsTest extends MicrometerMetricsTestBase { 44 | 45 | @Test 46 | public void shouldReportEventbusMetrics(TestContext context) { 47 | metricsOptions.addLabels(Label.EB_ADDRESS, Label.EB_FAILURE, Label.CLASS_NAME); 48 | 49 | vertx = vertx(context).exceptionHandler(t -> { 50 | if (t.getMessage() == null || !t.getMessage().contains("expected failure")) { 51 | context.exceptionHandler().handle(t); 52 | } 53 | }); 54 | 55 | int instances = 2; 56 | 57 | Async ebReady = context.async(instances); 58 | Async allReceived = context.async(instances); 59 | // Setup eventbus handler 60 | vertx.deployVerticle(() -> new AbstractVerticle() { 61 | @Override 62 | public void start(Promise startPromise) { 63 | vertx.eventBus().consumer("testSubject", msg -> { 64 | JsonObject body = (JsonObject) msg.body(); 65 | try { 66 | Thread.sleep(body.getLong("sleep")); 67 | } catch (InterruptedException e) { 68 | throw new RuntimeException(e); 69 | } 70 | if (body.containsKey("last")) { 71 | allReceived.countDown(); 72 | } 73 | if (body.getBoolean("fail")) { 74 | throw new RuntimeException("It's ok! [expected failure]"); 75 | } 76 | }).completion().onComplete(startPromise); 77 | } 78 | }, new DeploymentOptions().setInstances(instances)).onComplete(context.asyncAssertSuccess(v -> ebReady.complete())); 79 | 80 | ebReady.awaitSuccess(); 81 | // Send to eventbus 82 | vertx.eventBus().publish("testSubject", new JsonObject("{\"fail\": false, \"sleep\": 30}")); 83 | vertx.eventBus().publish("testSubject", new JsonObject("{\"fail\": false, \"sleep\": 30}")); 84 | vertx.eventBus().publish("testSubject", new JsonObject("{\"fail\": false, \"sleep\": 10}")); 85 | vertx.eventBus().publish("testSubject", new JsonObject("{\"fail\": false, \"sleep\": 30}")); 86 | vertx.eventBus().publish("testSubject", new JsonObject("{\"fail\": true, \"sleep\": 10}")); 87 | vertx.eventBus().publish("testSubject", new JsonObject("{\"fail\": false, \"sleep\": 30}")); 88 | vertx.eventBus().publish("testSubject", new JsonObject("{\"fail\": true, \"sleep\": 10}")); 89 | vertx.eventBus().request("no handler", new JsonObject("{\"fail\": false, \"sleep\": 30}")); 90 | vertx.eventBus().request("no handler", new JsonObject("{\"fail\": false, \"sleep\": 30}")); 91 | vertx.eventBus().publish("testSubject", new JsonObject("{\"fail\": false, \"sleep\": 30, \"last\": true}")); 92 | allReceived.awaitSuccess(); 93 | 94 | waitForValue(context, "vertx.eventbus.processed[address=testSubject,side=local]$COUNT", 95 | value -> value.intValue() == 8 * instances); 96 | List datapoints = listDatapoints(startsWith("vertx.eventbus")); 97 | assertThat(datapoints).hasSize(13).contains( 98 | dp("vertx.eventbus.handlers[address=testSubject]$VALUE", instances), 99 | dp("vertx.eventbus.pending[address=testSubject,side=local]$VALUE", 0), 100 | dp("vertx.eventbus.pending[address=testSubject,side=remote]$VALUE", 0), 101 | dp("vertx.eventbus.sent[address=no handler,side=local]$COUNT", 2), 102 | dp("vertx.eventbus.published[address=testSubject,side=local]$COUNT", 8), 103 | dp("vertx.eventbus.received[address=no handler,side=local]$COUNT", 2), 104 | dp("vertx.eventbus.received[address=testSubject,side=local]$COUNT", 8), 105 | dp("vertx.eventbus.delivered[address=testSubject,side=local]$COUNT", 8), 106 | dp("vertx.eventbus.reply.failures[address=no handler,failure=NO_HANDLERS]$COUNT", 2), 107 | dp("vertx.eventbus.processed[address=testSubject,side=local]$COUNT", 8d * instances), 108 | dp("vertx.eventbus.processed[address=testSubject,side=remote]$COUNT", 0), 109 | dp("vertx.eventbus.discarded[address=testSubject,side=local]$COUNT", 0), 110 | dp("vertx.eventbus.discarded[address=testSubject,side=remote]$COUNT", 0)); 111 | } 112 | 113 | @Test 114 | public void shouldDiscardMessages(TestContext context) { 115 | vertx = vertx(context); 116 | 117 | int num = 10; 118 | EventBus eb = vertx.eventBus(); 119 | MessageConsumer consumer = eb.consumer(new MessageConsumerOptions() 120 | .setAddress("foo") 121 | .setMaxBufferedMessages(num)); 122 | consumer.pause(); 123 | consumer.handler(msg -> fail("should not be called")); 124 | for (int i = 0; i < num; i++) { 125 | eb.send("foo", "the_message-" + i); 126 | } 127 | eb.send("foo", "last"); 128 | 129 | waitForValue(context, "vertx.eventbus.discarded[side=local]$COUNT", value -> value.intValue() == 1); 130 | List datapoints = listDatapoints(startsWith("vertx.eventbus")); 131 | assertThat(datapoints).contains(dp("vertx.eventbus.pending[side=local]$VALUE", 10D)); 132 | 133 | // Unregister => discard all remaining 134 | consumer.unregister(); 135 | waitForValue(context, "vertx.eventbus.discarded[side=local]$COUNT", value -> value.intValue() == 11); 136 | datapoints = listDatapoints(startsWith("vertx.eventbus")); 137 | assertThat(datapoints).contains(dp("vertx.eventbus.pending[side=local]$VALUE", 0)); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/impl/VertxHttpClientMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2023 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.micrometer.impl; 18 | 19 | import io.micrometer.core.instrument.*; 20 | import io.micrometer.core.instrument.Meter.MeterProvider; 21 | import io.micrometer.core.instrument.Timer.Sample; 22 | import io.vertx.core.http.WebSocket; 23 | import io.vertx.core.net.SocketAddress; 24 | import io.vertx.core.spi.metrics.ClientMetrics; 25 | import io.vertx.core.spi.metrics.HttpClientMetrics; 26 | import io.vertx.core.spi.observability.HttpRequest; 27 | import io.vertx.core.spi.observability.HttpResponse; 28 | import io.vertx.micrometer.impl.VertxHttpClientMetrics.RequestMetric; 29 | import io.vertx.micrometer.impl.VertxNetClientMetrics.NetClientSocketMetric; 30 | import io.vertx.micrometer.impl.tags.Labels; 31 | 32 | import java.util.concurrent.atomic.LongAdder; 33 | import java.util.function.Function; 34 | 35 | import static io.vertx.micrometer.Label.*; 36 | import static io.vertx.micrometer.MetricsDomain.HTTP_CLIENT; 37 | 38 | /** 39 | * @author Joel Takvorian 40 | */ 41 | class VertxHttpClientMetrics extends VertxNetClientMetrics implements HttpClientMetrics { 42 | 43 | private final Function> customTagsProvider; 44 | private final MeterProvider requestCount; 45 | private final MeterProvider requestBytes; 46 | private final MeterProvider responseTime; 47 | private final MeterProvider responseCount; 48 | private final MeterProvider responseBytes; 49 | 50 | VertxHttpClientMetrics(AbstractMetrics parent, String metricsName, Function> customTagsProvider, String localAddress) { 51 | super(parent, metricsName, HTTP_CLIENT, localAddress); 52 | this.customTagsProvider = customTagsProvider; 53 | requestCount = Counter.builder(names.getHttpRequestsCount()) 54 | .description("Number of requests sent") 55 | .withRegistry(registry); 56 | requestBytes = DistributionSummary.builder(names.getHttpRequestBytes()) 57 | .description("Size of requests in bytes") 58 | .withRegistry(registry); 59 | responseTime = Timer.builder(names.getHttpResponseTime()) 60 | .description("Response time") 61 | .withRegistry(registry); 62 | responseCount = Counter.builder(names.getHttpResponsesCount()) 63 | .description("Response count with codes") 64 | .withRegistry(registry); 65 | responseBytes = DistributionSummary.builder(names.getHttpResponseBytes()) 66 | .description("Size of responses in bytes") 67 | .withRegistry(registry); 68 | } 69 | 70 | @Override 71 | public ClientMetrics createEndpointMetrics(SocketAddress remoteAddress, int maxPoolSize) { 72 | Tags endPointTags = local; 73 | if (enabledLabels.contains(REMOTE)) { 74 | endPointTags = endPointTags.and(REMOTE.toString(), Labels.address(remoteAddress)); 75 | } 76 | return new EndpointMetrics(endPointTags); 77 | } 78 | 79 | @Override 80 | public LongAdder connected(WebSocket webSocket) { 81 | Tags tags = local; 82 | if (enabledLabels.contains(REMOTE)) { 83 | tags = tags.and(REMOTE.toString(), Labels.address(webSocket.remoteAddress())); 84 | } 85 | LongAdder wsConnections = longGaugeBuilder(names.getHttpActiveWsConnections(), LongAdder::doubleValue) 86 | .description("Number of websockets currently opened") 87 | .tags(tags) 88 | .register(registry); 89 | wsConnections.increment(); 90 | return wsConnections; 91 | } 92 | 93 | @Override 94 | public void disconnected(LongAdder wsConnections) { 95 | wsConnections.decrement(); 96 | } 97 | 98 | class EndpointMetrics implements ClientMetrics { 99 | 100 | final Tags endPointTags; 101 | 102 | EndpointMetrics(Tags endPointTags) { 103 | this.endPointTags = endPointTags; 104 | } 105 | 106 | @Override 107 | public RequestMetric requestBegin(String uri, HttpRequest request) { 108 | Tags tags = endPointTags; 109 | if (enabledLabels.contains(HTTP_PATH)) { 110 | tags = tags.and(HTTP_PATH.toString(), HttpUtils.parsePath(request.uri())); 111 | } 112 | if (enabledLabels.contains(HTTP_METHOD)) { 113 | tags = tags.and(HTTP_METHOD.toString(), request.method().toString()); 114 | } 115 | if (customTagsProvider != null) { 116 | tags = tags.and(customTagsProvider.apply(request)); 117 | } 118 | RequestMetric requestMetric = new RequestMetric(tags); 119 | requestMetric.requests.increment(); 120 | requestCount.withTags(tags).increment(); 121 | return requestMetric; 122 | } 123 | 124 | @Override 125 | public void requestEnd(RequestMetric requestMetric, long bytesWritten) { 126 | requestBytes.withTags(requestMetric.tags).record(bytesWritten); 127 | if (requestMetric.requestEnded()) { 128 | requestMetric.requests.decrement(); 129 | } 130 | } 131 | 132 | @Override 133 | public void requestReset(RequestMetric requestMetric) { 134 | requestMetric.requests.decrement(); 135 | requestMetric.requestReset(); 136 | } 137 | 138 | @Override 139 | public void responseBegin(RequestMetric requestMetric, HttpResponse response) { 140 | requestMetric.responseBegin(response); 141 | } 142 | 143 | @Override 144 | public void responseEnd(RequestMetric requestMetric, long bytesRead) { 145 | if (requestMetric.responseEnded()) { 146 | requestMetric.requests.decrement(); 147 | } 148 | responseCount.withTags(requestMetric.responseTags).increment(); 149 | requestMetric.sample.stop(responseTime.withTags(requestMetric.responseTags)); 150 | responseBytes.withTags(requestMetric.responseTags).record(bytesRead); 151 | } 152 | 153 | } 154 | 155 | class RequestMetric { 156 | 157 | final Tags tags; 158 | 159 | final LongAdder requests; 160 | final Sample sample; 161 | 162 | Tags responseTags; 163 | boolean responseEnded; 164 | boolean requestEnded; 165 | boolean reset; 166 | 167 | RequestMetric(Tags tags) { 168 | this.tags = tags; 169 | responseTags = tags; 170 | requests = longGaugeBuilder(names.getHttpActiveRequests(), LongAdder::doubleValue) 171 | .description("Number of requests waiting for a response") 172 | .tags(tags) 173 | .register(registry); 174 | sample = Timer.start(); 175 | } 176 | 177 | void requestReset() { 178 | reset = true; 179 | } 180 | 181 | boolean requestEnded() { 182 | requestEnded = true; 183 | return !reset && responseEnded; 184 | } 185 | 186 | void responseBegin(HttpResponse response) { 187 | if (enabledLabels.contains(HTTP_CODE)) { 188 | responseTags = responseTags.and(HTTP_CODE.toString(), String.valueOf(response.statusCode())); 189 | } 190 | } 191 | 192 | boolean responseEnded() { 193 | responseEnded = true; 194 | return !reset && requestEnded; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/micrometer/backends/BackendRegistries.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.vertx.micrometer.backends; 18 | 19 | import io.micrometer.core.instrument.Meter; 20 | import io.micrometer.core.instrument.MeterRegistry; 21 | import io.micrometer.core.instrument.config.MeterFilter; 22 | import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; 23 | import io.vertx.micrometer.*; 24 | 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.concurrent.ConcurrentHashMap; 28 | import java.util.function.Function; 29 | import java.util.regex.Pattern; 30 | 31 | /** 32 | * {@link BackendRegistries} is responsible for managing registries related to particular micrometer backends (influxdb, prometheus...) 33 | * It contains a store of {@link BackendRegistry} objects, each of whose encapsulating a micrometer's {@link MeterRegistry} 34 | * @author Joel Takvorian 35 | */ 36 | public final class BackendRegistries { 37 | private static final Map REGISTRIES = new ConcurrentHashMap<>(); 38 | 39 | private BackendRegistries() { 40 | } 41 | 42 | /** 43 | * Create a new backend registry, containing a micrometer registry, initialized with the provided options. 44 | * If a registry already exists with the associated name, it is just returned without any effect. 45 | * @param options micrometer options, including configuration related to the backend. 46 | * Should be a subclass of {@link MicrometerMetricsOptions} (ex: {@link VertxInfluxDbOptions}, {@link VertxPrometheusOptions}). 47 | * If the class is not recognized, a {@link NoopBackendRegistry} will be returned. 48 | * @return the created (or existing) {@link BackendRegistry} 49 | */ 50 | public static BackendRegistry setupBackend(MicrometerMetricsOptions options, MeterRegistry meterRegistry) { 51 | return REGISTRIES.computeIfAbsent(options.getRegistryName(), k -> { 52 | final BackendRegistry reg; 53 | if (meterRegistry != null) { 54 | if (options.getPrometheusOptions() != null && meterRegistry instanceof PrometheusMeterRegistry) { 55 | // If a Prometheus registry is provided, extra initialization steps may have to be performed 56 | reg = new PrometheusBackendRegistry(options.getPrometheusOptions(), (PrometheusMeterRegistry) meterRegistry); 57 | } else { 58 | // Other backend registries have no special extra steps 59 | reg = () -> meterRegistry; 60 | } 61 | } else if (options.getInfluxDbOptions() != null && options.getInfluxDbOptions().isEnabled()) { 62 | reg = new InfluxDbBackendRegistry(options.getInfluxDbOptions()); 63 | } else if (options.getPrometheusOptions() != null && options.getPrometheusOptions().isEnabled()) { 64 | reg = new PrometheusBackendRegistry(options.getPrometheusOptions()); 65 | } else if (options.getJmxMetricsOptions() != null && options.getJmxMetricsOptions().isEnabled()) { 66 | reg = new JmxBackendRegistry(options.getJmxMetricsOptions()); 67 | } else { 68 | // No backend setup, use global registry 69 | reg = NoopBackendRegistry.INSTANCE; 70 | } 71 | registerMatchers(reg.getMeterRegistry(), options.getLabelMatches()); 72 | return reg; 73 | }); 74 | } 75 | 76 | /** 77 | * Get the default micrometer registry. 78 | * May return {@code null} if it hasn't been registered yet or if it has been stopped. 79 | * @return the micrometer registry or {@code null} 80 | */ 81 | public static MeterRegistry getDefaultNow() { 82 | return getNow(MicrometerMetricsOptions.DEFAULT_REGISTRY_NAME); 83 | } 84 | 85 | /** 86 | * Get the micrometer registry of the given name. 87 | * May return {@code null} if it hasn't been registered yet or if it has been stopped. 88 | * @param registryName the name associated with this registry in Micrometer options 89 | * @return the micrometer registry or {@code null} 90 | */ 91 | public static MeterRegistry getNow(String registryName) { 92 | BackendRegistry backendRegistry = REGISTRIES.get(registryName); 93 | if (backendRegistry != null) { 94 | return backendRegistry.getMeterRegistry(); 95 | } 96 | return null; 97 | } 98 | 99 | /** 100 | * Stop (unregister) the backend registry of the given name. 101 | * Any resource started by this backend registry will be released (like running HTTP server) 102 | * @param registryName the name associated with this registry in Micrometer options 103 | */ 104 | public static void stop(String registryName) { 105 | BackendRegistry reg = REGISTRIES.remove(registryName); 106 | if (reg != null) { 107 | reg.close(); 108 | } 109 | } 110 | 111 | public static void registerMatchers(MeterRegistry registry, List matches) { 112 | matches.forEach(m -> { 113 | switch (m.getType()) { 114 | case EQUALS: 115 | if (m.getAlias() == null) { 116 | // Exact match => accept 117 | registry.config().meterFilter(MeterFilter.deny(id -> { 118 | if (m.getDomain() != null && !id.getName().startsWith(m.getDomain().getPrefix())) { 119 | // If domain has been specified and we're not in that domain, ignore rule 120 | return false; 121 | } 122 | String tagValue = id.getTag(m.getLabel()); 123 | return !m.getValue().equals(tagValue); 124 | })); 125 | } else { 126 | // Exact match => alias 127 | registry.config().meterFilter(replaceTagValues( 128 | m.getDomain(), 129 | m.getLabel(), 130 | val -> { 131 | if (m.getValue().equals(val)) { 132 | return m.getAlias(); 133 | } 134 | return val; 135 | } 136 | )); 137 | } 138 | break; 139 | case REGEX: 140 | Pattern pattern = Pattern.compile(m.getValue()); 141 | if (m.getAlias() == null) { 142 | // Regex match => accept 143 | registry.config().meterFilter(MeterFilter.accept(id -> { 144 | if (m.getDomain() != null && !id.getName().startsWith(m.getDomain().getPrefix())) { 145 | // If domain has been specified and we're not in that domain, ignore rule 146 | return true; 147 | } 148 | String tagValue = id.getTag(m.getLabel()); 149 | if (tagValue == null) { 150 | return false; 151 | } 152 | return pattern.matcher(tagValue).matches(); 153 | })); 154 | } else { 155 | // Regex match => alias 156 | registry.config().meterFilter(replaceTagValues( 157 | m.getDomain(), 158 | m.getLabel(), 159 | val -> { 160 | if (pattern.matcher(val).matches()) { 161 | return m.getAlias(); 162 | } 163 | return val; 164 | } 165 | )); 166 | } 167 | break; 168 | } 169 | }); 170 | } 171 | 172 | private static MeterFilter replaceTagValues(MetricsDomain domain, String tagKey, Function replacement) { 173 | return new MeterFilter() { 174 | @Override 175 | public Meter.Id map(Meter.Id id) { 176 | if (domain != null && !id.getName().startsWith(domain.getPrefix())) { 177 | return id; 178 | } 179 | return MeterFilter.replaceTagValues(tagKey, replacement).map(id); 180 | } 181 | }; 182 | } 183 | } 184 | --------------------------------------------------------------------------------