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