Skip to content

Commit

Permalink
Add metrics support for Netty 4.x
Browse files Browse the repository at this point in the history
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<EventExecutor>`
(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
  • Loading branch information
bclozel committed Apr 6, 2023
1 parent b8ad913 commit d985e62
Show file tree
Hide file tree
Showing 7 changed files with 737 additions and 0 deletions.
2 changes: 2 additions & 0 deletions micrometer-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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<EventExecutor> eventExecutors;

/**
* Create a binder instance for the given event executors.
* <p>
* An {@link io.netty.channel.EventLoopGroup} (all its executors) can be instrumented
* at startup like: <pre>
* MeterRegistry registry = //...
* EventLoopGroup group = //...
* new NettyEventExecutorMetrics(group).bindTo(registry);
* </pre> 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. <pre>
* &#064;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);
* }
* //...
* }
* </pre>
* @param eventExecutors the event executors to instrument
*/
public NettyEventExecutorMetrics(Iterable<EventExecutor> 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);
}
});
}

}
Loading

0 comments on commit d985e62

Please sign in to comment.