From d985e6237266f2504e29c1bd2f13bacdd8a00f86 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 4 Apr 2023 21:23:09 +0200 Subject: [PATCH] Add metrics support for Netty 4.x This commit adds two new `MeterBinder` implementations for instrumenting Netty 4.x: `NettyAllocatorMetrics` and `NettyEventExecutorMetrics`. `NettyAllocatorMetrics` will instrument any `ByteBufAllocatorMetricProvider` and gather information about heap/direct memory allocated; additional metrics are provided for pooled allocators. `NettyEventExecutorMetrics` will instrument `Iterable` (typically, `EventLoop` or `EventLoopGroup` instances) and count the number of pending tasks for all. Metrics and tags are described in the `NettyMeters` class. Closes gh-522 --- micrometer-core/build.gradle | 2 + .../binder/netty4/NettyAllocatorMetrics.java | 118 ++++++++ .../netty4/NettyEventExecutorMetrics.java | 78 ++++++ .../instrument/binder/netty4/NettyMeters.java | 257 ++++++++++++++++++ .../binder/netty4/package-info.java | 25 ++ .../netty4/NettyAllocatorMetricsTests.java | 179 ++++++++++++ .../NettyEventExecutorMetricsTests.java | 78 ++++++ 7 files changed, 737 insertions(+) create mode 100644 micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyAllocatorMetrics.java create mode 100644 micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetrics.java create mode 100644 micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyMeters.java create mode 100644 micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/package-info.java create mode 100644 micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyAllocatorMetricsTests.java create mode 100644 micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetricsTests.java diff --git a/micrometer-core/build.gradle b/micrometer-core/build.gradle index 314ff16fb1..2d426bf302 100644 --- a/micrometer-core/build.gradle +++ b/micrometer-core/build.gradle @@ -57,6 +57,7 @@ jar { org.bson.*;resolution:=dynamic;version="${@}",\ rx.*;resolution:=dynamic;version="${@}",\ javax.persistence.*;resolution:=dynamic;version="${@}",\ + io.netty.*;resolution:=dynamic;version="${@}",\ * '''.stripIndent() } @@ -98,6 +99,7 @@ dependencies { optionalApi 'org.apache.tomcat.embed:tomcat-embed-core' optionalApi 'org.glassfish.jersey.core:jersey-server' optionalApi 'io.grpc:grpc-api' + optionalApi 'io.netty:netty-transport' // apache httpcomponents monitoring optionalApi 'org.apache.httpcomponents:httpclient' diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyAllocatorMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyAllocatorMetrics.java new file mode 100644 index 0000000000..19bf3e1108 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyAllocatorMetrics.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument.binder.netty4; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.binder.MeterBinder; +import io.netty.buffer.ByteBufAllocatorMetric; +import io.netty.buffer.ByteBufAllocatorMetricProvider; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.buffer.PooledByteBufAllocatorMetric; + +/** + * {@link MeterBinder} for Netty memory allocators. + * + * @author Brian Clozel + * @since 1.11.0 + * @see NettyMeters + */ +public class NettyAllocatorMetrics implements MeterBinder { + + private final ByteBufAllocatorMetricProvider allocator; + + /** + * Create a binder instance for the given allocator. + * @param allocator the {@code ByteBuf} allocator to instrument + */ + public NettyAllocatorMetrics(ByteBufAllocatorMetricProvider allocator) { + this.allocator = allocator; + } + + @Override + public void bindTo(MeterRegistry registry) { + int allocatorId = this.allocator.hashCode(); + + ByteBufAllocatorMetric allocatorMetric = this.allocator.metric(); + Tags tags = Tags.of(NettyMeters.AllocatorMeterTags.ID.asString(), String.valueOf(allocatorId), + NettyMeters.AllocatorMeterTags.ALLOCATOR_TYPE.asString(), this.allocator.getClass().getSimpleName()); + + Gauge + .builder(NettyMeters.ALLOCATOR_MEMORY_USED.getName(), allocatorMetric, + ByteBufAllocatorMetric::usedHeapMemory) + .tags(tags.and(NettyMeters.AllocatorMemoryMeterTags.MEMORY_TYPE.asString(), "heap")) + .register(registry); + + Gauge + .builder(NettyMeters.ALLOCATOR_MEMORY_USED.getName(), allocatorMetric, + ByteBufAllocatorMetric::usedDirectMemory) + .tags(tags.and(NettyMeters.AllocatorMemoryMeterTags.MEMORY_TYPE.asString(), "direct")) + .register(registry); + + if (this.allocator instanceof PooledByteBufAllocator) { + PooledByteBufAllocator pooledByteBufAllocator = (PooledByteBufAllocator) this.allocator; + PooledByteBufAllocatorMetric pooledAllocatorMetric = pooledByteBufAllocator.metric(); + + Gauge + .builder(NettyMeters.ALLOCATOR_MEMORY_PINNED.getName(), pooledByteBufAllocator, + PooledByteBufAllocator::pinnedHeapMemory) + .tags(tags.and(NettyMeters.AllocatorMemoryMeterTags.MEMORY_TYPE.asString(), "heap")) + .register(registry); + + Gauge + .builder(NettyMeters.ALLOCATOR_MEMORY_PINNED.getName(), pooledByteBufAllocator, + PooledByteBufAllocator::pinnedDirectMemory) + .tags(tags.and(NettyMeters.AllocatorMemoryMeterTags.MEMORY_TYPE.asString(), "direct")) + .register(registry); + + Gauge + .builder(NettyMeters.ALLOCATOR_POOLED_ARENAS.getName(), pooledAllocatorMetric, + PooledByteBufAllocatorMetric::numHeapArenas) + .tags(tags.and(NettyMeters.AllocatorMemoryMeterTags.MEMORY_TYPE.asString(), "heap")) + .register(registry); + Gauge + .builder(NettyMeters.ALLOCATOR_POOLED_ARENAS.getName(), pooledAllocatorMetric, + PooledByteBufAllocatorMetric::numDirectArenas) + .tags(tags.and(NettyMeters.AllocatorMemoryMeterTags.MEMORY_TYPE.asString(), "direct")) + .register(registry); + + Gauge + .builder(NettyMeters.ALLOCATOR_POOLED_CACHE_SIZE.getName(), pooledAllocatorMetric, + PooledByteBufAllocatorMetric::normalCacheSize) + .tags(tags.and(NettyMeters.AllocatorPooledCacheMeterTags.CACHE_TYPE.asString(), "normal")) + .register(registry); + Gauge + .builder(NettyMeters.ALLOCATOR_POOLED_CACHE_SIZE.getName(), pooledAllocatorMetric, + PooledByteBufAllocatorMetric::smallCacheSize) + .tags(tags.and(NettyMeters.AllocatorPooledCacheMeterTags.CACHE_TYPE.asString(), "small")) + .register(registry); + + Gauge + .builder(NettyMeters.ALLOCATOR_POOLED_THREADLOCAL_CACHES.getName(), pooledAllocatorMetric, + PooledByteBufAllocatorMetric::numThreadLocalCaches) + .tags(tags) + .register(registry); + + Gauge + .builder(NettyMeters.ALLOCATOR_POOLED_CHUNK_SIZE.getName(), pooledAllocatorMetric, + PooledByteBufAllocatorMetric::chunkSize) + .tags(tags) + .register(registry); + } + } + +} diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetrics.java new file mode 100644 index 0000000000..23c250a68d --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetrics.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument.binder.netty4; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.binder.MeterBinder; +import io.netty.channel.EventLoop; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.SingleThreadEventExecutor; + +/** + * {@link MeterBinder} for Netty event executors. + * + * @author Brian Clozel + * @since 1.11.0 + * @see NettyMeters + */ +public class NettyEventExecutorMetrics implements MeterBinder { + + private final Iterable eventExecutors; + + /** + * Create a binder instance for the given event executors. + *

+ * An {@link io.netty.channel.EventLoopGroup} (all its executors) can be instrumented + * at startup like:

+     * MeterRegistry registry = //...
+     * EventLoopGroup group = //...
+     * new NettyEventExecutorMetrics(group).bindTo(registry);
+     * 
Alternatively, an {@link EventLoop} can be instrumented at runtime during + * channel initialization. In this case, developers should ensure that this instance + * has not been registered already as re-binding metrics at runtime is inefficient + * here.
+     * @Override
+     * public void initChannel(SocketChannel channel) throws Exception {
+     *   // this concurrent check must be implemented by micrometer users
+     *   if (!isEventLoopInstrumented(channel.eventLoop())) {
+     *     new EventExecutorMetrics(channel.eventLoop()).bindTo(registry);
+     *   }
+     *   //...
+     * }
+     * 
+ * @param eventExecutors the event executors to instrument + */ + public NettyEventExecutorMetrics(Iterable eventExecutors) { + this.eventExecutors = eventExecutors; + } + + @Override + public void bindTo(MeterRegistry registry) { + this.eventExecutors.forEach(eventExecutor -> { + if (eventExecutor instanceof SingleThreadEventExecutor) { + SingleThreadEventExecutor singleThreadEventExecutor = (SingleThreadEventExecutor) eventExecutor; + Gauge + .builder(NettyMeters.EVENT_EXECUTOR_TASKS_PENDING.getName(), + singleThreadEventExecutor::pendingTasks) + .tag(NettyMeters.EventLoopTasksPendingMeterTags.NAME.asString(), + singleThreadEventExecutor.threadProperties().name()) + .register(registry); + } + }); + } + +} diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyMeters.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyMeters.java new file mode 100644 index 0000000000..23ed450919 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyMeters.java @@ -0,0 +1,257 @@ +/* + * Copyright 2023 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument.binder.netty4; + +import io.micrometer.common.docs.KeyName; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.docs.MeterDocumentation; + +/** + * Meter documentation for Netty 4. + * + * @author Brian Clozel + * @since 1.11.0 + * @see NettyAllocatorMetrics + * @see NettyEventExecutorMetrics + */ +public enum NettyMeters implements MeterDocumentation { + + /** + * Size of memory used by the allocator, in bytes. + */ + ALLOCATOR_MEMORY_USED { + @Override + public String getName() { + return "netty.allocator.memory.used"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.GAUGE; + } + + @Override + public String getBaseUnit() { + return "bytes"; + } + + @Override + public KeyName[] getKeyNames() { + return KeyName.merge(AllocatorMeterTags.values(), AllocatorMemoryMeterTags.values()); + } + }, + + /** + * Size of memory used by allocated buffers, in bytes. + */ + ALLOCATOR_MEMORY_PINNED { + @Override + public String getName() { + return "netty.allocator.memory.pinned"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.GAUGE; + } + + @Override + public String getBaseUnit() { + return "bytes"; + } + + @Override + public KeyName[] getKeyNames() { + return KeyName.merge(AllocatorMeterTags.values(), AllocatorMemoryMeterTags.values()); + } + }, + + /** + * Number of Arenas for a pooled allocator. + */ + ALLOCATOR_POOLED_ARENAS { + @Override + public String getName() { + return "netty.allocator.pooled.arenas"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.GAUGE; + } + + @Override + public KeyName[] getKeyNames() { + return KeyName.merge(AllocatorMeterTags.values(), AllocatorMemoryMeterTags.values()); + } + }, + + /** + * Size of the cache for a pooled allocator, in bytes. + */ + ALLOCATOR_POOLED_CACHE_SIZE { + @Override + public String getName() { + return "netty.allocator.pooled.cache.size"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.GAUGE; + } + + @Override + public String getBaseUnit() { + return "bytes"; + } + + @Override + public KeyName[] getKeyNames() { + return KeyName.merge(AllocatorMeterTags.values(), AllocatorPooledCacheMeterTags.values()); + } + }, + + /** + * Number of ThreadLocal caches for a pooled allocator. + */ + ALLOCATOR_POOLED_THREADLOCAL_CACHES { + @Override + public String getName() { + return "netty.allocator.pooled.threadlocal.caches"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.GAUGE; + } + + @Override + public KeyName[] getKeyNames() { + return AllocatorMeterTags.values(); + } + }, + + /** + * Size of memory chunks for a pooled allocator, in bytes. + */ + ALLOCATOR_POOLED_CHUNK_SIZE { + @Override + public String getName() { + return "netty.allocator.pooled.chunk.size"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.GAUGE; + } + + @Override + public String getBaseUnit() { + return "bytes"; + } + + @Override + public KeyName[] getKeyNames() { + return AllocatorMeterTags.values(); + } + }, + + /** + * Number of pending tasks in the event executor. + */ + EVENT_EXECUTOR_TASKS_PENDING { + @Override + public String getName() { + return "netty.eventexecutor.tasks.pending"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.GAUGE; + } + + @Override + public KeyName[] getKeyNames() { + return EventLoopTasksPendingMeterTags.values(); + } + }; + + public enum AllocatorMeterTags implements KeyName { + + /** + * Unique runtime identifier for the allocator. + */ + ID { + @Override + public String asString() { + return "id"; + } + }, + /** + * Type of memory allocation strategy (pooled, unpooled). + */ + ALLOCATOR_TYPE { + @Override + public String asString() { + return "allocator.type"; + } + } + + } + + public enum AllocatorMemoryMeterTags implements KeyName { + + /** + * Type of memory allocated: {@code "heap"} memory or {@code "direct"} memory. + */ + MEMORY_TYPE { + @Override + public String asString() { + return "memory.type"; + } + } + + } + + public enum AllocatorPooledCacheMeterTags implements KeyName { + + /** + * Type of cache pages for this cache. + */ + CACHE_TYPE { + @Override + public String asString() { + return "cache.type"; + } + } + + } + + public enum EventLoopTasksPendingMeterTags implements KeyName { + + /** + * Event Loop name. + */ + NAME { + @Override + public String asString() { + return "name"; + } + } + + } + +} diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/package-info.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/package-info.java new file mode 100644 index 0000000000..3482f5a14f --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Meter binders for Netty 4.x. + */ +@NonNullApi +@NonNullFields +package io.micrometer.core.instrument.binder.netty4; + +import io.micrometer.common.lang.NonNullApi; +import io.micrometer.common.lang.NonNullFields; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyAllocatorMetricsTests.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyAllocatorMetricsTests.java new file mode 100644 index 0000000000..e2945f483a --- /dev/null +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyAllocatorMetricsTests.java @@ -0,0 +1,179 @@ +/* + * Copyright 2023 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument.binder.netty4; + +import io.micrometer.core.instrument.MockClock; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.simple.SimpleConfig; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.buffer.UnpooledByteBufAllocator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link NettyAllocatorMetrics}. + * + * @author Brian Clozel + */ +class NettyAllocatorMetricsTests { + + private SimpleMeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); + + @Test + void shouldHaveHeapMemoryUsedMetricsForUnpooledAllocator() { + UnpooledByteBufAllocator unpooledByteBufAllocator = new UnpooledByteBufAllocator(false); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(unpooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = unpooledByteBufAllocator.buffer(); + Tags tags = Tags.of("id", String.valueOf(unpooledByteBufAllocator.hashCode()), "allocator.type", + "UnpooledByteBufAllocator", "memory.type", "heap"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_MEMORY_USED.getName()).tags(tags).gauge().value()) + .isPositive(); + buffer.release(); + } + + @Test + void shouldHaveDirectMemoryUsedMetricsForUnpooledAllocator() { + UnpooledByteBufAllocator unpooledByteBufAllocator = new UnpooledByteBufAllocator(false); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(unpooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = unpooledByteBufAllocator.directBuffer(); + Tags tags = Tags.of("id", String.valueOf(unpooledByteBufAllocator.hashCode()), "allocator.type", + "UnpooledByteBufAllocator", "memory.type", "direct"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_MEMORY_USED.getName()).tags(tags).gauge().value()) + .isPositive(); + buffer.release(); + } + + @Test + void shouldHaveHeapMemoryUsedMetricsForPooledAllocator() { + PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(pooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = pooledByteBufAllocator.buffer(); + Tags tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator", "memory.type", "heap"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_MEMORY_USED.getName()).tags(tags).gauge().value()) + .isPositive(); + buffer.release(); + } + + @Test + void shouldHaveDirectMemoryUsedMetricsForPooledAllocator() { + PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(pooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = pooledByteBufAllocator.directBuffer(); + Tags tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator", "memory.type", "direct"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_MEMORY_USED.getName()).tags(tags).gauge().value()) + .isPositive(); + buffer.release(); + } + + @Test + void shouldHaveHeapMemoryPinnedMetricsForPooledAllocator() { + PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(pooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = pooledByteBufAllocator.buffer(); + Tags tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator", "memory.type", "heap"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_MEMORY_PINNED.getName()).tags(tags).gauge().value()) + .isPositive(); + buffer.release(); + } + + @Test + void shouldHaveDirectMemoryPinnedMetricsForPooledAllocator() { + PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(pooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = pooledByteBufAllocator.directBuffer(); + Tags tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator", "memory.type", "direct"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_MEMORY_PINNED.getName()).tags(tags).gauge().value()) + .isPositive(); + buffer.release(); + } + + @Test + void shouldHaveArenasMetricsForPooledAllocator() { + PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(pooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = pooledByteBufAllocator.buffer(); + Tags tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator", "memory.type", "heap"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_POOLED_ARENAS.getName()).tags(tags).gauge().value()) + .isEqualTo(pooledByteBufAllocator.metric().numHeapArenas()); + + tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator", "memory.type", "direct"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_POOLED_ARENAS.getName()).tags(tags).gauge().value()) + .isEqualTo(pooledByteBufAllocator.metric().numDirectArenas()); + buffer.release(); + } + + @Test + void shouldHaveCacheSizeMetricsForPooledAllocator() { + PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(pooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = pooledByteBufAllocator.buffer(); + Tags tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator", "cache.type", "normal"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_POOLED_CACHE_SIZE.getName()).tags(tags).gauge().value()) + .isEqualTo(pooledByteBufAllocator.metric().normalCacheSize()); + + tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator", "cache.type", "small"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_POOLED_CACHE_SIZE.getName()).tags(tags).gauge().value()) + .isEqualTo(pooledByteBufAllocator.metric().smallCacheSize()); + buffer.release(); + } + + @Test + void shouldHaveThreadlocalCachesMetricsForPooledAllocator() { + PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(pooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = pooledByteBufAllocator.buffer(); + Tags tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator"); + assertThat( + this.registry.get(NettyMeters.ALLOCATOR_POOLED_THREADLOCAL_CACHES.getName()).tags(tags).gauge().value()) + .isEqualTo(pooledByteBufAllocator.metric().numThreadLocalCaches()); + buffer.release(); + } + + @Test + void shouldHaveChunkSizeMetricsForPooledAllocator() { + PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(); + NettyAllocatorMetrics binder = new NettyAllocatorMetrics(pooledByteBufAllocator); + binder.bindTo(this.registry); + ByteBuf buffer = pooledByteBufAllocator.buffer(); + Tags tags = Tags.of("id", String.valueOf(pooledByteBufAllocator.hashCode()), "allocator.type", + "PooledByteBufAllocator"); + assertThat(this.registry.get(NettyMeters.ALLOCATOR_POOLED_CHUNK_SIZE.getName()).tags(tags).gauge().value()) + .isEqualTo(pooledByteBufAllocator.metric().chunkSize()); + buffer.release(); + } + +} diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetricsTests.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetricsTests.java new file mode 100644 index 0000000000..04dc0deb5a --- /dev/null +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetricsTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument.binder.netty4; + +import io.micrometer.core.instrument.MockClock; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.simple.SimpleConfig; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.channel.EventLoop; +import io.netty.util.concurrent.SingleThreadEventExecutor; +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link NettyEventExecutorMetrics}. + * + * @author Brian Clozel + */ +class NettyEventExecutorMetricsTests { + + private SimpleMeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); + + @Test + void shouldHaveTaskPendingMetricForEachEventLoop() throws Exception { + Set names = new LinkedHashSet<>(); + DefaultEventLoopGroup eventExecutors = new DefaultEventLoopGroup(); + new NettyEventExecutorMetrics(eventExecutors).bindTo(this.registry); + eventExecutors.spliterator().forEachRemaining(eventExecutor -> { + if (eventExecutor instanceof SingleThreadEventExecutor) { + SingleThreadEventExecutor singleThreadEventExecutor = (SingleThreadEventExecutor) eventExecutor; + names.add(singleThreadEventExecutor.threadProperties().name()); + } + }); + names.forEach(name -> { + assertThat(this.registry.get(NettyMeters.EVENT_EXECUTOR_TASKS_PENDING.getName()) + .tags(Tags.of("name", name)) + .gauge() + .value()).isZero(); + }); + eventExecutors.shutdownGracefully().get(5, TimeUnit.SECONDS); + } + + @Test + void shouldHaveTaskPendingMetricForSingleEventLoop() throws Exception { + DefaultEventLoopGroup eventExecutors = new DefaultEventLoopGroup(); + EventLoop eventLoop = eventExecutors.next(); + new NettyEventExecutorMetrics(eventLoop).bindTo(this.registry); + if (eventLoop instanceof SingleThreadEventExecutor) { + SingleThreadEventExecutor singleThreadEventExecutor = (SingleThreadEventExecutor) eventLoop; + String eventLoopName = singleThreadEventExecutor.threadProperties().name(); + assertThat(this.registry.get(NettyMeters.EVENT_EXECUTOR_TASKS_PENDING.getName()) + .tags(Tags.of("name", eventLoopName)) + .gauge() + .value()).isZero(); + } + eventExecutors.shutdownGracefully().get(5, TimeUnit.SECONDS); + } + +}