Repository navigation
[ISSUE #11474] feat(proxy): expose call metrics for the admin gRPC interface - #11475
Felix-Gong wants to merge 1 commit into
Conversation
…RPC interface The proxy exports an OpenTelemetry pipeline in both modes but registers only the rocketmq_proxy_up gauge: call rate, latency and error rate of the admin gRPC server are invisible, so a healthy-looking up gauge says nothing about auth rejections or failing admin calls. Add ProxyAdminMetricsInterceptor to the admin server and two instruments registered through the existing initMetrics path: rocketmq_proxy_admin_calls_total and rocketmq_proxy_admin_rt (milliseconds), labeled with the bare method name and the terminal invocation_status on top of the node/cluster/proxy_mode attributes. The interceptor wraps close(Status) and records exactly once per call (the cancel/close race is settled by an AtomicBoolean), and is registered after the auth interceptor so gRPC reverse-order execution makes it the outer wrapper that also counts rejected calls. Listener callbacks that throw are recorded before the exception is rethrown, because GlobalExceptionInterceptor closes the call it holds directly and would otherwise bypass the close wrapper. Instruments default to NopLongCounter/NopLongHistogram, so disabled metrics or a call served before manager start is a free no-op. LABEL_MAP becomes a ConcurrentHashMap: recording now reads it on request threads while start() populates it. Tests: ProxyAdminMetricsInterceptorTest 7/7 (OK and PERMISSION_DENIED recording, cancel/close dedup in both orders, delegate close propagation, builtin method full name, thrown exceptions), a test MetricReader asserts the exported names, labels and values, GrpcServerInterceptorOrderTest pins the auth/metrics relative order. Full proxy module: 465 green with checkstyle, spotbugs and misspell.
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Review by github-manager-bot
Summary
This PR adds OpenTelemetry metrics instrumentation to the admin gRPC server in the proxy, making control plane operations observable through the existing metrics pipeline.
Findings
- [Info] proxy/src/main/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminMetricsInterceptor.java:38-45 — The AtomicBoolean pattern for deduplication is correct and handles the cancel/close race condition properly.
- [Info] proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java:62 — Changing LABEL_MAP from HashMap to ConcurrentHashMap is a good catch for thread safety during concurrent initialization and access.
- [Info] proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java:145-152 — Using NopLongCounter/NopLongHistogram as defaults is excellent defensive programming, preventing NPE when metrics are disabled.
- [Info] proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java:131-134 — The interceptor ordering is correct: registering metrics after auth means it runs before auth (gRPC executes in reverse order), so rejected calls are properly counted.
Suggestions
The implementation is solid. The comprehensive test coverage (12 tests) covering normal paths, error paths, race conditions, and metrics export is particularly well done. The documentation clearly explains the design decisions around interceptor ordering and the AtomicBoolean deduplication pattern.
Cross-repo Note
No proto changes required, so no coordination needed with rocketmq-clients.
Automated review by github-manager-bot
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Review by github-manager-bot
Summary
Adds OpenTelemetry metrics for the admin gRPC server: rocketmq_proxy_admin_calls_total (counter) and rocketmq_proxy_admin_rt (histogram), labeled by method and invocation status. Registered as the outermost interceptor so it counts calls rejected by auth.
Findings
- [Info] The
AtomicBooleanguard inProxyAdminMetricsInterceptoris correct — ensures exactly-once recording per call even when both cancel and close fire. The "first terminal event wins" semantics are well-documented. - [Info] Interceptor ordering is correct: metrics registered after auth means it executes before auth (gRPC reverse order), so rejected calls are counted. The test
metricsInterceptorIsRegisteredAfterAuthInterceptorTestpins this ordering. - [Info]
LABEL_MAPchanged fromHashMaptoConcurrentHashMapis a good catch — admin calls read it on request threads whilestart()populates it in cluster mode. - [Info] NopLongCounter/NopLongHistogram defaults are a clean pattern — calls before metrics init or with metrics disabled are free no-ops instead of NPEs.
- [Info] The
statusCodeOfmethod correctly extracts the code fromStatusRuntimeExceptionand falls back toINTERNALfor other exceptions. This ensures service-side failures are not a blind spot. - [Info] Test coverage is comprehensive: OK/PERMISSION_DENIED recording, cancel/close dedup in both orders, exception recording, builtin method name handling, and end-to-end metric export verification.
Suggestions
- Consider adding a brief comment in
ProxyStartup.javaexplaining why the metrics interceptor is registered after auth (the reverse execution order is non-obvious and future maintainers might reorder them). The test documents this, but a code comment would make it explicit at the registration site. - The
recordAdminCallmethod ispublic staticand could be called from anywhere. Consider adding@VisibleForTestingor making it package-private if it's only meant to be called from the interceptor.
Overall this is a solid addition to proxy observability. The design is safe, well-tested, and follows the existing metrics patterns.
Automated review by github-manager-bot
|
Hi @RongtongJin @lizhimins — this PR (and the two related fixes in the same area, #11469 and #11471) has been sitting with I've run the full For context: this adds OpenTelemetry call metrics for the admin gRPC server ( |
Fixes #11474
The proxy ships an OpenTelemetry pipeline (
ProxyMetricsManager, both local and cluster mode) whose only instrument is therocketmq_proxy_upgauge: call rate, latency and error rate of the admin gRPC server — the surface every ops tool talks to — are invisible. This PR makes the control plane observable through the pipeline that already exists, with no proto change and no new dependency.What lands:
initMetricsso both init paths (initLocalModeand clusterstart()) get them:rocketmq_proxy_admin_calls_total(counter) androcketmq_proxy_admin_rt(histogram,milliseconds), labeledmethod+invocation_statuson top of the usual node/cluster/proxy_modeattributes fromnewAttributesBuilder().ProxyAdminMetricsInterceptoron the admin server: wrapsclose(Status)and records the terminal code (OK,PERMISSION_DENIED,INTERNAL, ...) exactly once per call — a client cancel reaching the listener first wins theAtomicBooleanrace, so a handler that closes afterwards cannot double-count. Cancelled-without-close calls are not dropped either.ProxyAdminAuthInterceptorinconfigInterceptor(...). gRPC executes interceptors in reverse registration order, so metrics is the outer wrapper and counts calls the auth interceptor rejects — precisely where failures would show up first.GlobalExceptionInterceptorsits outside and closes the call it holds directly (bypassing theclosewrapper), so listener callbacks that throw are recorded before the exception is rethrown to it — service-side INTERNAL failures are not a blind spot.LABEL_MAPbecomes aConcurrentHashMap. Admin call recording now reads it on request threads whilestart()populates it (in cluster mode the gRPC servers are appended to the start list before the metrics manager), which the pre-existingHashMapdoes not support.NopLongCounter/NopLongHistogram(the broker's idiom), so metrics disabled or a call served before manager start is a free no-op instead of an NPE.Evidence:
ProxyAdminMetricsInterceptorTest7/7: OK and PERMISSION_DENIED recorded throughclose, cancel/close dedup in both orders, delegateclosestill propagated, builtin method without a bare name records the full name, service exceptions recorded asINTERNAL(andStatusRuntimeExceptioncodes preserved) and rethrown.ProxyAdminCallMetricsTest4/4: record pre-init lands in Nops,initLocalModeswaps in real instruments,DISABLEkeeps Nops, and an end-to-end pull through a testMetricReaderasserts the exported names (rocketmq_proxy_admin_calls_total,rocketmq_proxy_admin_rt), themethod/invocation_statuslabel values and the recorded count/latency.GrpcServerInterceptorOrderTest: extended with the two-post-interceptor relative order this PR depends on (auth registered first, metrics second → metrics executes outermost), so a re-registration cannot silently stop counting rejected calls.proxymodule: 465 tests green on JDK 11 with checkstyle (validate) and spotbugs (compile) enabled — the same gatesmvn packageruns in CI. Zero new spotbugs findings,misspell -errorclean.Coordination with open work:
ProxyStartupimport hunk is adjacent to this PR's import line (trivial both-keep merge), itsProxyMetricsConstant/ProxyMetricsManagerentries carry no content change, and itsClientAdminMetricsRecorderinstruments the new endpoint at service level — orthogonal to this wire-level interceptor, and calls to that endpoint would still flow through it.start()'s label/header parsing — untouched regions in this PR. Both add aProxyMetricsManagerTest, which is why this PR deliberately names its testProxyAdminCallMetricsTest.