Skip to content

feat(sdk): serve Go-compatible gRPC server metrics - #10

Merged
jonasz-lasut merged 1 commit into
mainfrom
grpc-server-metrics
Aug 26, 2026
Merged

jonasz-lasut merged 1 commit into
mainfrom
grpc-server-metrics

Conversation

@jonasz-lasut

Copy link
Copy Markdown
Collaborator

Closes the last recorded Go-parity gap (README "Differences from function-sdk-go", the ABI research verdict): a function built with this SDK now serves the same gRPC server metrics a function-sdk-go function serves, so platform dashboards and alerts work across Go and Rust functions unchanged.

What changed

  • A private metrics module: grpc_server_started_total, grpc_server_handled_total (with grpc_code), grpc_server_msg_received_total, grpc_server_msg_sent_total - Confirmed (function-sdk-go v0.7.1 source): the exact names, label sets and help strings of grpcprometheus.NewServerMetrics() with default options, which is what the Go SDK installs.
  • A tower layer around the whole router (so health Check is counted like Go's server-wide interceptor), reading the unary status from the grpc-status trailer or header; a client that disappears mid-call counts as Canceled.
  • A /metrics endpoint beside the gRPC server: Args gains metrics_address (default :8080 - the Go SDK's DefaultMetricsAddress - METRICS_ADDRESS env, empty disables), mirroring how Serve() behaves in Go.
  • Built on prometheus-client 0.25, per the recorded research verdict: the only purpose-built alternative, tonic-prometheus-layer, is pinned to tonic ^0.13 and would hold the SDK's tonic major back.

Deliberate parity choices

  • Unary-only, because the Go SDK installs only metrics.UnaryServerInterceptor() - streaming methods (reflection, health Watch) were never counted there and are not counted here.
  • Zero-initialization like metrics.InitializeMetrics(srv): every served method exists as a zero series from startup, streaming methods included (permanently zero).
  • No grpc_server_handling_seconds: the Go SDK never enables the handling-time histogram.
  • Recorded divergences: this SDK serves no v1beta1, so the v1beta1 series the Go SDK pre-created do not exist; and the exposition format is OpenMetrics 1.0 first (readable beyond Prometheus), with the classic text/plain; version=0.0.4 served to an Accept header that asks for it without accepting OpenMetrics - the series are what Go compatibility means, and both formats carry them identically. Registration compensates two prometheus-client behaviors verified in its source (it appends _total to counter samples and a trailing . to help strings), so the served text matches the Go SDK's byte for byte.

API surface

One new public field: Args::metrics_address. The module itself is private - no layer, registry or encoder types are exported, per the keep-serve-alone surface decision (#4). Breaking for Args struct literals (downstream: function-wasm constructs Args in tests and defines its own colliding --metrics-address flag - it drops that flag and adopts the SDK's on its next version bump), so this is a 0.3.0.

Testing

  • The serve integration test now drives the full flow: one RunFunction + one health Check, then asserts on the served /metrics - started/received/handled-OK/sent all exactly 1 for RunFunction, the health Check counted, Watch present at zero, OpenMetrics content type + # EOF, and the classic rendering on an explicit text/plain Accept.
  • Unit tests: grpc-go code-name mapping, the unary allowlist, format negotiation, the OpenMetrics/classic shape (single trailing help period included), and Canceled-on-drop without double-counting.
  • cargo test --workspace green (6 suites); cargo clippy --workspace --all-targets -- -D warnings and cargo fmt --check clean.

Reference

  • The layer is the same design proven in the function-wasm runtime (feat: serve the Go runtime gRPC server metrics jonasz-lasut/function-wasm#58 and #61, merged), adapted to this server's fixed service set. Deliberately not shared as a library: the runtime keeps its own copy per the minimal-surface decision - lift a common abstraction only when a second real consumer needs one.
  • Author's note: implemented by Claude (Fable).

The gRPC server series function-sdk-go serves, with the interceptor
exact names, labels and help strings, so dashboards work across Go and
Rust functions: a tower layer over the whole router (unary-only like
the Go interceptor, InitializeMetrics-style zero series, no handling
histogram - Go never enabled it), counted into a prometheus-client
registry and served at /metrics on --metrics-address (the Go SDK
default :8080, empty disables) - OpenMetrics 1.0 as the main format,
the classic Prometheus text format for an Accept header that asks.
Closes the last recorded Go-parity gap. Breaking for Args literals:
the struct gains metrics_address.
@jonasz-lasut
jonasz-lasut merged commit e553231 into main Aug 26, 2026
4 checks passed
@jonasz-lasut
jonasz-lasut deleted the grpc-server-metrics branch August 26, 2026 11:08
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.

1 participant