Skip to content

feat(metrics): client WebSocket request, connection and per-client notification metrics - #1

Draft
snowkide wants to merge 4 commits into
mainfrom
feat/ws-client-metrics
Draft

snowkide wants to merge 4 commits into
mainfrom
feat/ws-client-metrics

Conversation

@snowkide

Copy link
Copy Markdown

Client-side WebSocket traffic is only partly visible in metrics: calls that reach the network are counted in erpc_network_request_received_total indistinguishably from HTTP, and calls rejected before the network (invalid JSON, disallowed method, auth failure, consumer rate limit, network unavailable) are not counted at all. The subscription pressure metrics from erpc#1255 cover connections, subscriptions and notifications per network, but not per client, and not why connections close.

This builds on erpc#1255 instead of adding parallel metrics:

  • transport label (http|ws) on erpc_network_request_received_total, recorded on NormalizedRequest (defaults to http).
  • erpc_websocket_connections_active gains a network label. The connection's network label is now set once at upgrade instead of on the first subscription, so connections without subscriptions are attributed too.
  • erpc_websocket_connections_closed_total{project,network,close_code,initiator}. close_code is bounded to the RFC 6455 range (1000-1015, else other).
  • erpc_websocket_requests_total{project,network,category,user,agent_name,outcome}: every JSON-RPC request on a client connection (batch items individually), including pre-network rejections. Outcomes: ok, error, rate_limited, invalid, invalid_batch, method_not_allowed, unauthorized, network_unavailable. category stays n/a until the request is authenticated, so unauthenticated clients cannot create series with arbitrary method names.
  • user and agent_name on erpc_websocket_notifications_sent_total and erpc_websocket_subscription_notifications_dropped_total, which become labeled counters. The labels are captured at eth_subscribe time and carried on the subscription (wsclient.SubscriptionLabels).

Notes for review:

  • Every new per-client label is on a labeled counter, so deployments that do not want per-user series drop user/agent_name with the existing metrics.customizations label rules. Gauges carry no user label.
  • The network label is read from the project config (the value Network.Label() returns). Resolving it with GetNetwork would block the upgrade until a cold network is bootstrapped.
  • getOrCreateConn no longer sets networkLabel; it is written once before the connection is shared, so reads need no lock.
  • No new latency histogram: per-user latency is already available from erpc_network_request_duration_seconds{user}.
  • Docs: Metrics section in operation/websocket, rows in reference/metrics, and the transport label wherever network_request_received_total labels are listed.

Verification

Based on erpc/erpc main @ 094b912.

  • go build ./..., go vet ./... and gofmt -l on touched files are clean.
  • make test-fast (what CI runs): passes, exit 0, no failing test.
  • New/updated tests:
    • TestWebSocket_RequestAccounting: ok, method_not_allowed, invalid and invalid_batch outcomes; transport="ws" on received; connection gauge up and back down; client-initiated close counted with code 1000.
    • TestWebSocket_RequestAccountingAuthRateLimited: the rate_limited outcome.
    • TestWebSocket_CloseCodeLabelIsBounded.
    • TestWebSocket_LogsSubscribeOnAllUpstreamsAndDedupe: the sent counter now asserts the user/agent_name labels.
    • TestNormalizedRequest_TransportDefaultsToHttp.

AI assistance

  • Model: anthropic/claude-opus-5.5
  • Harness / skills: Claude Code
  • Human-reviewed: no, pending

🤖 Generated with Claude Code

snowkide and others added 4 commits October 15, 2026 09:46
Requests received over a client WebSocket connection are counted in
erpc_network_request_received_total like HTTP ones, with no way to tell
the two apart. Record the ingress transport on NormalizedRequest
(defaulting to "http") and expose it as a transport label (http|ws), set
by the WebSocket handler for regular calls and eth_(un)subscribe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- erpc_websocket_connections_active gains a network label. The label is
  now set once at upgrade (the network alias if configured, else its id),
  so connections without subscriptions are attributed too.
- erpc_websocket_connections_closed_total{project,network,close_code,
  initiator}: closed client connections, by who ended them. Codes outside
  the RFC 6455 range (1000-1015) are reported as "other" so a client cannot
  create series by picking arbitrary close codes.
- erpc_websocket_requests_total{project,network,category,user,agent_name,
  outcome}: every JSON-RPC request received on a client connection, batch
  items individually, including those rejected before they reach the
  network and so absent from network_request_received_total (invalid,
  invalid_batch, method_not_allowed, unauthorized, rate_limited,
  network_unavailable). category stays "n/a" until the request is
  authenticated so unauthenticated clients cannot create series with
  arbitrary method names. A panicking request is counted as "error".

The network label is read from the project config (the value
Network.Label() returns): resolving it via GetNetwork would block the
upgrade until a cold network is bootstrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
erpc_websocket_notifications_sent_total and
erpc_websocket_subscription_notifications_dropped_total gain user and
agent_name, and become labeled counters so those labels can be dropped
through metrics customizations like on every other per-client counter.

The labels are captured once at eth_subscribe time and carried on the
subscription (wsclient.SubscriptionLabels), so recording a notification
needs no lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a Metrics section to the WebSocket page and list the new families in
the metrics reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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