Skip to content

Conversation

arjan-bal
Copy link
Contributor

@arjan-bal arjan-bal commented Oct 9, 2025

This PR improves performance by eliminating heap allocations when multiple stats handlers are configured.

Previously, iterating through a list of handlers caused one heap allocation per handler for each RPC. This change introduces a Handler that combines multiple Handlers and implements the Handler interface. The combined handler delegates calls to the handlers it contains.

This approach allows gRPC clients and servers to operate as if there were only a single Handler registered, simplifying the internal logic and removing the per-RPC allocation overhead. To avoid any performance impact when stats are disabled, the combined Handler is only created when at least one handler is registered.

RELEASE NOTES: N/A

@arjan-bal arjan-bal changed the title stasts: Re-use objects while calling multiple Handlers stats: Re-use objects while calling multiple Handlers Oct 9, 2025
@arjan-bal arjan-bal added Type: Performance Performance improvements (CPU, network, memory, etc) Area: Observability Includes Stats, Tracing, Channelz, Healthz, Binlog, Reflection, Admin, GCP Observability labels Oct 9, 2025
@arjan-bal arjan-bal modified the milestone: 1.77 Release Oct 9, 2025
Copy link

codecov bot commented Oct 9, 2025

Codecov Report

❌ Patch coverage is 98.54015% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.95%. Comparing base (0e9ac5d) to head (38cc04a).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
internal/transport/http2_client.go 90.47% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8639      +/-   ##
==========================================
+ Coverage   81.28%   81.95%   +0.67%     
==========================================
  Files         415      416       +1     
  Lines       40888    40760     -128     
==========================================
+ Hits        33234    33404     +170     
+ Misses       6162     5995     -167     
+ Partials     1492     1361     -131     
Files with missing lines Coverage Δ
clientconn.go 89.72% <100.00%> (-0.09%) ⬇️
internal/stats/stats.go 100.00% <100.00%> (ø)
internal/transport/handler_server.go 91.16% <100.00%> (+0.31%) ⬆️
internal/transport/http2_server.go 90.67% <100.00%> (-0.48%) ⬇️
internal/transport/transport.go 83.87% <ø> (ø)
server.go 81.90% <100.00%> (-0.18%) ⬇️
stream.go 81.52% <100.00%> (-0.05%) ⬇️
internal/transport/http2_client.go 92.14% <90.47%> (+8.00%) ⬆️

... and 31 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@arjan-bal arjan-bal force-pushed the stats-handlers-reuse-event-objects branch 2 times, most recently from a36c3d1 to 88fb8c2 Compare October 9, 2025 16:37
@arjan-bal arjan-bal force-pushed the stats-handlers-reuse-event-objects branch from 88fb8c2 to 38cc04a Compare October 9, 2025 16:39
@arjan-bal arjan-bal requested a review from easwars October 9, 2025 16:49
@arjan-bal arjan-bal requested a review from dfawley October 9, 2025 18:21
@easwars
Copy link
Contributor

easwars commented Oct 10, 2025

Do you have any benchmark numbers for this change?

Copy link
Contributor

@easwars easwars left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I was wondering what if you exported the combinedHandler and used it as a concrete type instead of as stats.Handler? The grpc server and the channel could use this concrete type, and any other internal type could as well. For external facing APIs that are expecting a stats.Handler, it should still continue to work if the caller has the concrete type. But again, I don't know if this will have any bearing on the performance. So, if this is going to increase the scope of the PR, please push back. Thanks.

}
for _, sh := range t.statsHandlers {
t.ctx = sh.TagConn(t.ctx, &stats.ConnTagInfo{
if t.statsHandler != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we made the combined handler's methods work for a nil receiver? It will remove a bunch of nil checks, but I don't know if or how that affects performance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm .. I guess the nil check is much cheaper that a function call. So, I guess we are good. But if you have more insights, please share.

Comment on lines +47 to +49
for _, h := range ch.handlers {
ctx = h.TagRPC(ctx, info)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All methods of this combinedHandler still iterates through the list of handlers and invokes them in turn. So, how does this not result in heap allocations while the previous approach did?

Comment on lines 909 to 914
header, ok := metadata.FromOutgoingContext(ctx)
if ok {
header.Set("user-agent", t.userAgent)
} else {
header = metadata.Pairs("user-agent", t.userAgent)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that we add this header only when stats handlers are configured. Do you happen to know why?

"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/serviceconfig"
extstats "google.golang.org/grpc/stats"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rename, extstats, is very similar to expstats which we use in a bunch of other places to import experimental/stats package. Can we come up with a better name?

@easwars easwars assigned arjan-bal and unassigned easwars and dfawley Oct 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Observability Includes Stats, Tracing, Channelz, Healthz, Binlog, Reflection, Admin, GCP Observability Type: Performance Performance improvements (CPU, network, memory, etc)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants