Skip to content

[ISSUE #11474] feat(proxy): expose call metrics for the admin gRPC interface - #11475

Open
Felix-Gong wants to merge 1 commit into
apache:developfrom
Felix-Gong:feat/proxy-admin-grpc-call-metrics
Open

Felix-Gong wants to merge 1 commit into
apache:developfrom
Felix-Gong:feat/proxy-admin-grpc-call-metrics

Conversation

@Felix-Gong

Copy link
Copy Markdown
Contributor

Fixes #11474

The proxy ships an OpenTelemetry pipeline (ProxyMetricsManager, both local and cluster mode) whose only instrument is the rocketmq_proxy_up gauge: 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:

  • Two instruments, registered through the existing initMetrics so both init paths (initLocalMode and cluster start()) get them: rocketmq_proxy_admin_calls_total (counter) and rocketmq_proxy_admin_rt (histogram, milliseconds), labeled method + invocation_status on top of the usual node/cluster/proxy_mode attributes from newAttributesBuilder().
  • ProxyAdminMetricsInterceptor on the admin server: wraps close(Status) and records the terminal code (OK, PERMISSION_DENIED, INTERNAL, ...) exactly once per call — a client cancel reaching the listener first wins the AtomicBoolean race, so a handler that closes afterwards cannot double-count. Cancelled-without-close calls are not dropped either.
  • Ordering: registered after ProxyAdminAuthInterceptor in configInterceptor(...). 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. GlobalExceptionInterceptor sits outside and closes the call it holds directly (bypassing the close wrapper), so listener callbacks that throw are recorded before the exception is rethrown to it — service-side INTERNAL failures are not a blind spot.
  • One-line hardening: LABEL_MAP becomes a ConcurrentHashMap. Admin call recording now reads it on request threads while start() populates it (in cluster mode the gRPC servers are appended to the start list before the metrics manager), which the pre-existing HashMap does not support.
  • Disabled-safe by construction: instruments default to 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:

  • ProxyAdminMetricsInterceptorTest 7/7: OK and PERMISSION_DENIED recorded through close, cancel/close dedup in both orders, delegate close still propagated, builtin method without a bare name records the full name, service exceptions recorded as INTERNAL (and StatusRuntimeException codes preserved) and rethrown.
  • ProxyAdminCallMetricsTest 4/4: record pre-init lands in Nops, initLocalMode swaps in real instruments, DISABLE keeps Nops, and an end-to-end pull through a test MetricReader asserts the exported names (rocketmq_proxy_admin_calls_total, rocketmq_proxy_admin_rt), the method/invocation_status label 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.
  • Full proxy module: 465 tests green on JDK 11 with checkstyle (validate) and spotbugs (compile) enabled — the same gates mvn package runs in CI. Zero new spotbugs findings, misspell -error clean.

Coordination with open work:

…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 RockteMQ-AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 RockteMQ-AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 AtomicBoolean guard in ProxyAdminMetricsInterceptor is 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 metricsInterceptorIsRegisteredAfterAuthInterceptorTest pins this ordering.
  • [Info] LABEL_MAP changed from HashMap to ConcurrentHashMap is a good catch — admin calls read it on request threads while start() 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 statusCodeOf method correctly extracts the code from StatusRuntimeException and falls back to INTERNAL for 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.java explaining 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 recordAdminCall method is public static and could be called from anywhere. Consider adding @VisibleForTesting or 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

@Felix-Gong

Copy link
Copy Markdown
Contributor Author

Hi @RongtongJin @lizhimins — this PR (and the two related fixes in the same area, #11469 and #11471) has been sitting with action_required since it was opened, so the workflows have never actually run on it. Could you approve the workflow when you get a chance?

I've run the full proxy module locally with checkstyle and spotbugs enabled — 465 tests green — so I expect the checks to pass, and I'm happy to fix anything they flag.

For context: this adds OpenTelemetry call metrics for the admin gRPC server (rocketmq_proxy_admin_calls_total and rocketmq_proxy_admin_rt, recorded by a new interceptor), so the control plane stops being the only invisible surface in the metrics pipeline. The AI review already came back green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Expose call metrics for the proxy admin gRPC interface

2 participants