Conversation
79c1fc3 to
9ea1193
Compare
02c63cc to
8e7bbd6
Compare
- it determines the worker count by checking if the provided `Iterable` is an `instanceof MultithreadEventLoopGroup`, allowing it to safely call the `executorCount()` method for an accurate value - resolve test deprecations Signed-off-by: ttaehee <ttaehee1105@gmail.com>
8e7bbd6 to
eb7f487
Compare
|
@divijvaidya would this implementation work for what you had in mind when opening #6375? |
shakuzen
left a comment
There was a problem hiding this comment.
Looks good to me. Thank you for a very complete pull request. I'll wait a bit to see if we can get feedback from the original author of the issue requesting this metric.
|
|
||
| @Override | ||
| public void bindTo(MeterRegistry registry) { | ||
| if (this.eventExecutors instanceof MultithreadEventLoopGroup) { |
There was a problem hiding this comment.
Is MultithreadEventLoopGroup the right class for all sorts of event loop groups? I am curious to understand why we didn't choose EventLoopGroup here.
There was a problem hiding this comment.
Hi,
That's a great question. I chose MultithreadEventLoopGroup for two main reasons:
-
It aligns with the metric's purpose. The eventexecutor.workers metric is designed to measure multi-threaded worker pools. For single-threaded event loops, the value is always 1 and thus less meaningful for monitoring. Targeting MultithreadEventLoopGroup is the most logical approach as it’s the base for all widely-used, multi-threaded implementations.
-
Practicality: We need the executorCount() method to get the worker count. MultithreadEventLoopGroup provides this, but the general EventLoopGroup interface does not.
This approach ensures we're measuring what's actually meaningful while keeping the implementation straightforward. Thanks :)
There was a problem hiding this comment.
For single-threaded event loops, the value is always 1 and thus less meaningful for monitoring.
You are assuming that the user of the metric knows whether the underlying implementation is single threaded or multi-threaded. Micrometer library can be used by all sorts of use cases, also in situations perhaps where the user of the metric may not know the implementation.
For the sake of ensuring that this metric doesn't require a conditional clause based on concrete implementation of event loop, I think there is value in representing single threaded loop here as well. From implementation perspective, you can choose to copy https://github.com/line/armeria/blob/024f86dedccc7897a6206770dd95852ca2ed66cc/core/src/main/java/com/linecorp/armeria/common/metric/EventLoopMetrics.java#L88 or have a special case for single event loop here.
There was a problem hiding this comment.
Hi @divijvaidya
Thanks for the great feedback!
I've updated the PR to make the metric more robust and expanded the tests to cover all the cases you mentioned.
- The implementation now supports all EventLoopGroup types by adding a generic fallback, while still keeping the fast-path for MultithreadEventLoopGroup.
- The new tests verify this works for subclasses and other Iterable types as well.
Let me know what you think.
| } | ||
|
|
||
| @Test | ||
| void shouldHaveWorkersMetric() throws Exception { |
There was a problem hiding this comment.
please add test for other sub-classes of MultiThreadIoEventLoopGroup for example, https://netty.io/4.1/api/io/netty/channel/epoll/EpollEventLoopGroup.html
The test should validate that the code works for subclasses as well as direct implementations
|
|
||
| @Override | ||
| public void bindTo(MeterRegistry registry) { | ||
| if (this.eventExecutors instanceof MultithreadEventLoopGroup) { |
There was a problem hiding this comment.
For single-threaded event loops, the value is always 1 and thus less meaningful for monitoring.
You are assuming that the user of the metric knows whether the underlying implementation is single threaded or multi-threaded. Micrometer library can be used by all sorts of use cases, also in situations perhaps where the user of the metric may not know the implementation.
For the sake of ensuring that this metric doesn't require a conditional clause based on concrete implementation of event loop, I think there is value in representing single threaded loop here as well. From implementation perspective, you can choose to copy https://github.com/line/armeria/blob/024f86dedccc7897a6206770dd95852ca2ed66cc/core/src/main/java/com/linecorp/armeria/common/metric/EventLoopMetrics.java#L88 or have a special case for single event loop here.
42060ec to
6799a8c
Compare
- Based on feedback, the 'eventexecutor.workers' metric now supports all EventLoopGroup types, not just MultithreadEventLoopGroup. - It retains the fast-path for the common case and adds a generic fallback that iterates over the group for an accurate count. - Tests are expanded to verify all code paths, including subclasses and the new fallback. Signed-off-by: ttaehee <ttaehee1105@gmail.com>
6799a8c to
d3c3a3d
Compare
divijvaidya
left a comment
There was a problem hiding this comment.
Thank you for making the change. Left one final comment
| int count = 0; | ||
| for (EventExecutor ignored : this.eventExecutors) { | ||
| count++; | ||
| } | ||
| return count; |
There was a problem hiding this comment.
There is no test which tests this part of the code. can you please add one
There was a problem hiding this comment.
Actually, there was already a test covering the fallback-path: shouldHaveWorkersMetricForNonMultithreadEventLoopGroup.
That said, I agree the intent wasn’t very clear.
To clarify, I replaced it with a simpler mock-based test: shouldCountWorkersForGenericIterableViaFallbackPath.
Thanks for flagging this!
- Replace shouldHaveWorkersMetricForNonMultithreadEventLoopGroup with a simpler, mock-based test (shouldCountWorkersForGenericIterableViaFallbackPath) to make the fallback path intent explicit Signed-off-by: ttaehee <ttaehee1105@gmail.com>
| .tags(Tags.of("name", name)) | ||
| .gauge() | ||
| .value()).isNaN()); | ||
| .value()).isEqualTo(0.0)); |
There was a problem hiding this comment.
Let's move this into a separate PR since it is unrelated.
There was a problem hiding this comment.
My apologies for the extra notification! I accidentally re-requested the review.
You're right to point out the test change. It was a necessary update, as my changes caused the original test to fail.
This simply aligns it with the new expected behavior.
There was a problem hiding this comment.
I see what's happening now, I think. However, this change to the test makes the test meaningless. The point of the test is to ensure that the metrics do not prevent the underlying object (executor) from being garbage collected. It's doing that by asserting that eventually after garbage collection is initiated, the underlying object will be garbage collected, and therefore the metric will return NaN.
See #6641. If the changes here are preventing it from being garbage collected, this should be changed so we aren't reintroducing that bug. Perhaps getWorkerCount can take Iterable<EventExecutor> as a parameter and the Gauge registration can pass it.
There was a problem hiding this comment.
@shakuzen You're right about the strong reference issue—thanks for pointing that out.
I've switched to using a WeakReference to resolve it, and I’ve reverted the test to assert isNaN() accordingly.
Let me know if this approach looks good to you.
There was a problem hiding this comment.
I've updated the code in a new commit with what I suggested. By default, we keep weak references of objects passed to Gauge builders as the backing object. That helps eliminate some boilerplate code that would otherwise be common to gauge functions in a lot of places.
There was a problem hiding this comment.
Great to see this merged!
Thank you both, @divijvaidya and @shakuzen, for all the help and guidance on this.
eaa366c to
6c0d4a2
Compare
- Use WeakReference in worker count gauge to allow executor garbage collection. Signed-off-by: ttaehee <ttaehee1105@gmail.com>
6c0d4a2 to
896822d
Compare
shakuzen
left a comment
There was a problem hiding this comment.
Thank you everyone who worked on this. This is a great example of open source working well. We ended up with something improved with each person's input, and now all users will be able to benefit from that.
|
Nice work @ttaehee 👍 |
| return ((MultithreadEventLoopGroup) executors).executorCount(); | ||
| } | ||
|
|
||
| int count = 0; |
There was a problem hiding this comment.
I know I'm late to the party but would it make sense to do this before iterating over the executors?
if (executors instanceof Collection) {
return ((Collection<EventExecutor>) executors).size();
}I mean is there a chance that executors will be a Collection or there is no implementation in Netty that would conform this?
There was a problem hiding this comment.
Hi @jonatan-ivanov
Thanks for the great feedback!
I think it might be more efficient to first check if executors is a Collection, which would avoid an unnecessary loop and also serve as a defensive check for any potential custom implementations.
Should I go ahead and open a new PR for this?
There was a problem hiding this comment.
Yes, feel free to send a new PR.
Signed-off-by: Johnny Lim <izeye@naver.com>
Problem
It is difficult to monitor the state of Netty event loop workers in real-time during production, and having this visibility would be helpful for debugging and root cause analysis of network issues.
For example, in our Netty-based WebSocket + STOMP + RabbitMQ environment, we encountered ConnectTimeoutException errors under load.
Being able to immediately check the status of workers would help us identify and resolve these problems more easily.
Solution
Add the eventexecutor.workers metric to expose the number of Netty event loop workers in real-time.
Rationale
This metric allows operators to quickly monitor the count and status of workers, enabling faster diagnosis and response to issues.
Ultimately, it improves system stability and operational efficiency.
Local Verification
I've confirmed locally that the new metric is registered correctly after applying the changes.
As you can see below, netty.eventexecutor.workers is successfully created with the expected value.
Resolves #6375