Skip to content

Commit f2ea418

Browse files
authored
Add eventexecutor workers metric for netty (#6612)
Add a gauge to monitor the number of event executor workers, to give users context around the runtime state without having to know the Netty configuration in use at that time. Resolves gh-6375 Signed-off-by: ttaehee <[email protected]>
1 parent 7a749bc commit f2ea418

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

docs/modules/ROOT/pages/reference/netty.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,21 @@ include::{include-java}/netty/NettyMetricsTests.java[tags=channelInstrumentation
2020
-----
2121

2222
CAUTION: If you use lazy instrumentation, you must ensure that you do not bind metrics for the same resource multiple times, as this can have runtime drawbacks.
23+
24+
== EventExecutor Metrics
25+
26+
The `NettyEventExecutorMetrics` binder collects metrics from Netty's `EventExecutor` instances:
27+
28+
.EventExecutor metrics
29+
|===
30+
|Metric |Type |Description
31+
32+
|`eventexecutor.workers`
33+
|Gauge
34+
|The number of event executor workers
35+
36+
|`eventexecutor.tasks.pending`
37+
|Gauge
38+
|The number of pending tasks in individual event executors
39+
40+
|===

micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetrics.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.micrometer.core.instrument.Tags;
2222
import io.micrometer.core.instrument.binder.MeterBinder;
2323
import io.netty.channel.EventLoop;
24+
import io.netty.channel.MultithreadEventLoopGroup;
2425
import io.netty.util.concurrent.EventExecutor;
2526
import io.netty.util.concurrent.SingleThreadEventExecutor;
2627

@@ -77,6 +78,13 @@ public NettyEventExecutorMetrics(Iterable<EventExecutor> eventExecutors, Iterabl
7778

7879
@Override
7980
public void bindTo(MeterRegistry registry) {
81+
Gauge
82+
.builder(NettyMeters.EVENT_EXECUTOR_WORKERS.getName(), this.eventExecutors,
83+
NettyEventExecutorMetrics::getExecutorCount)
84+
.description("Number of event executor workers")
85+
.tags(tags)
86+
.register(registry);
87+
8088
this.eventExecutors.forEach(eventExecutor -> {
8189
if (eventExecutor instanceof SingleThreadEventExecutor) {
8290
SingleThreadEventExecutor singleThreadEventExecutor = (SingleThreadEventExecutor) eventExecutor;
@@ -91,4 +99,16 @@ public void bindTo(MeterRegistry registry) {
9199
});
92100
}
93101

102+
private static double getExecutorCount(Iterable<EventExecutor> executors) {
103+
if (executors instanceof MultithreadEventLoopGroup) {
104+
return ((MultithreadEventLoopGroup) executors).executorCount();
105+
}
106+
107+
int count = 0;
108+
for (EventExecutor ignored : executors) {
109+
count++;
110+
}
111+
return count;
112+
}
113+
94114
}

micrometer-core/src/main/java/io/micrometer/core/instrument/binder/netty4/NettyMeters.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,26 @@ public Meter.Type getType() {
188188
public KeyName[] getKeyNames() {
189189
return EventExecutorTasksPendingKeyNames.values();
190190
}
191+
},
192+
193+
/**
194+
* Number of event executor workers.
195+
*/
196+
EVENT_EXECUTOR_WORKERS {
197+
@Override
198+
public String getName() {
199+
return "netty.eventexecutor.workers";
200+
}
201+
202+
@Override
203+
public Meter.Type getType() {
204+
return Meter.Type.GAUGE;
205+
}
206+
207+
@Override
208+
public KeyName[] getKeyNames() {
209+
return new KeyName[0];
210+
}
191211
};
192212

193213
enum AllocatorKeyNames implements KeyName {

micrometer-core/src/test/java/io/micrometer/core/instrument/binder/netty4/NettyEventExecutorMetricsTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,24 @@
1818
import io.micrometer.core.instrument.Tags;
1919
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2020
import io.netty.channel.EventLoop;
21+
import io.netty.channel.EventLoopGroup;
2122
import io.netty.channel.MultiThreadIoEventLoopGroup;
2223
import io.netty.channel.MultithreadEventLoopGroup;
2324
import io.netty.channel.local.LocalIoHandler;
25+
import io.netty.util.concurrent.DefaultThreadFactory;
26+
import io.netty.util.concurrent.EventExecutor;
2427
import io.netty.util.concurrent.SingleThreadEventExecutor;
2528
import org.junit.jupiter.api.Test;
2629

30+
import java.util.Arrays;
2731
import java.util.LinkedHashSet;
2832
import java.util.Set;
2933
import java.util.concurrent.TimeUnit;
3034

3135
import static io.micrometer.core.instrument.binder.netty4.NettyMeters.EVENT_EXECUTOR_TASKS_PENDING;
36+
import static io.micrometer.core.instrument.binder.netty4.NettyMeters.EVENT_EXECUTOR_WORKERS;
3237
import static org.assertj.core.api.Assertions.assertThat;
38+
import static org.mockito.Mockito.mock;
3339

3440
/**
3541
* Tests for {@link NettyEventExecutorMetrics}.
@@ -93,4 +99,56 @@ void shouldHaveCustomTags() throws Exception {
9399
eventExecutors.shutdownGracefully().get(5, TimeUnit.SECONDS);
94100
}
95101

102+
@Test
103+
void shouldHaveWorkersMetric() throws Exception {
104+
MultiThreadIoEventLoopGroup group = new MultiThreadIoEventLoopGroup(4, new DefaultThreadFactory("test-workers"),
105+
LocalIoHandler.newFactory());
106+
new NettyEventExecutorMetrics(group).bindTo(this.registry);
107+
108+
assertThat(this.registry.get(EVENT_EXECUTOR_WORKERS.getName()).gauge().value()).isEqualTo(4);
109+
110+
group.shutdownGracefully().get(5, TimeUnit.SECONDS);
111+
}
112+
113+
@Test
114+
void shouldHaveWorkersMetricWithCustomTags() throws Exception {
115+
MultiThreadIoEventLoopGroup group = new MultiThreadIoEventLoopGroup(2, new DefaultThreadFactory("test-workers"),
116+
LocalIoHandler.newFactory());
117+
Tags extraTags = Tags.of("testKey", "testValue");
118+
new NettyEventExecutorMetrics(group, extraTags).bindTo(this.registry);
119+
120+
assertThat(this.registry.get(EVENT_EXECUTOR_WORKERS.getName()).tags(extraTags).gauge().value()).isEqualTo(2);
121+
122+
group.shutdownGracefully().get(5, TimeUnit.SECONDS);
123+
}
124+
125+
@Test
126+
void shouldCountWorkersForGenericIterableViaFallbackPath() {
127+
EventExecutor mockExecutor1 = mock(EventExecutor.class);
128+
EventExecutor mockExecutor2 = mock(EventExecutor.class);
129+
Iterable<EventExecutor> genericIterable = Arrays.asList(mockExecutor1, mockExecutor2);
130+
131+
new NettyEventExecutorMetrics(genericIterable, Tags.empty()).bindTo(this.registry);
132+
133+
assertThat(this.registry.get(EVENT_EXECUTOR_WORKERS.getName()).gauge().value()).isEqualTo(2.0);
134+
}
135+
136+
@Test
137+
void shouldHaveWorkersMetricForSubclassOfMultiThreadEventLoopGroup() throws Exception {
138+
EventLoopGroup group = new SubclassOfMultiThreadIoEventLoopGroup(3);
139+
new NettyEventExecutorMetrics(group).bindTo(this.registry);
140+
141+
assertThat(this.registry.get(EVENT_EXECUTOR_WORKERS.getName()).gauge().value()).isEqualTo(3.0);
142+
143+
group.shutdownGracefully().get(5, TimeUnit.SECONDS);
144+
}
145+
146+
private static class SubclassOfMultiThreadIoEventLoopGroup extends MultiThreadIoEventLoopGroup {
147+
148+
SubclassOfMultiThreadIoEventLoopGroup(int nThreads) {
149+
super(nThreads, new DefaultThreadFactory("subclass-test-workers"), LocalIoHandler.newFactory());
150+
}
151+
152+
}
153+
96154
}

0 commit comments

Comments
 (0)