Reference
Metrics reference
AI agents: fetch https://docs.erpc.cloud/reference/metrics.llms.txt for the complete machine-readable version of this page (full configuration schema, defaults, worked examples, and source links). Append `.llms.txt` to any docs URL for the same treatment.AIFor agents: /reference/metrics.llms.txt

Metrics reference

eRPC exposes a Prometheus /metrics endpoint on port 4001. Every subsystem emits counters, gauges, and histograms — upstream health, cache efficiency, rate limiting, consensus, hedging, selection scoring, and more. Drop any scraper on port 4001 and the full picture of your RPC fleet is immediately visible.

  • 141 metric definitions under the erpc_ namespace
  • One knob shapes the whole page: metrics.customizations drops families, projects labels, and overrides histogram buckets — a dropped family costs no series at all
  • High-cardinality labels (user, agent_name) are droppable fleet-wide and re-addable per metric
  • Stale series auto-evict every five minutes so cardinality stays bounded

Agent reference

Copy one of these prompts into your AI agent session (Claude Code, Cursor, …) — each one points the agent at this page's machine-readable reference so it can do the work correctly:

Prompt Example #1: wire up Grafana dashboards for my eRPC deployment
I want to build Grafana dashboards for my eRPC deployment — upstream health, cache
hit rate, request latency percentiles, and rate-limit events. Using the full metrics
catalog, generate PromQL queries for each panel and point out any cardinality footguns
I should avoid (like the block-range heatmap or CORS project label). Work with my existing eRPC config. Reference:
https://docs.erpc.cloud/reference/metrics.llms.txt
Prompt Example #2: reduce Prometheus cardinality without losing signal
My Prometheus is accumulating too many time series from eRPC histograms. Audit my
my eRPC config metrics config and recommend which metrics.customizations entries to add —
which labels to drop fleet-wide, which metrics to keep per-user latency on, and
how to tune buckets for my sub-100ms upstream fleet. Reference:
https://docs.erpc.cloud/reference/metrics.llms.txt
Prompt Example #3: alert on upstream health and cordon events
I need PagerDuty-ready alerts for upstream health in my eRPC setup: cordoned
upstreams, circuit-breaker state changes, and BDS hard-timeout spikes. Write PromQL
alert expressions referencing the correct metric names (watch out for stale names in
the bundled alert.rules file). Work with my existing eRPC config. Reference:
https://docs.erpc.cloud/reference/metrics.llms.txt
Prompt Example #4: debug a missing or always-zero metric
One of my eRPC metrics is always 0 and I can't tell if it's a config issue or a
dormant metric. Check the metrics reference for known dormant metrics and required
opt-in flags (like memory.emitMetrics), and tell me whether my eRPC config is missing
any config that would make the metric meaningful. Reference:
https://docs.erpc.cloud/reference/metrics.llms.txt
Metrics — full agent referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.

How it works

Initialization order. Every metric family is defined at package init and registered later, by a single telemetry.Configure call from erpc.Init. Definitions go through the Define* factories in telemetry/manager.go — DefineCounter, DefineGauge, DefineLabeledCounter, DefineLabeledHistogram — which record the family (with its kind) in the manager's index and return a usable, unregistered collector. Call sites hold that pointer for the process lifetime and never check whether it is registered.

Configure then, in order: compiles metrics.customizations and the deprecated label knobs into a single MetricPolicy (returning an error and registering nothing if an entry is malformed), resolves histogramBuckets, publishes the policy, and walks the definition index. For each family it skips the ones the policy drops, rebuilds the rest under the now-published policy — its label projection and, for a histogram, its per-family buckets — registers them with prometheus.DefaultRegisterer, and finally calls ResetHandleCache to invalidate handles pointing at the pre-rebuild Vecs. [telemetry/manager.go:L196-286]

The define/register split is forced by Prometheus: a registry freezes a family's label-set hash the first time it is registered and keeps it for the registry's lifetime (dimHashesByName survives Unregister). Anything that changes a label set — a labels rule, the deprecated histogramDropLabels/counterDropLabels — therefore has to be applied before the first registration. Dropping a family needs the same window for a different reason: the only way to keep a family off /metrics entirely is to never register it.

The policy engine. telemetry/policy.go owns every exposure, label and bucket decision, so there is exactly one precedence implementation. It compiles each customization into a rule carrying a compiled subject pattern (exact, prefix*, or *), an optional exposure action, its label rules and its buckets; the deprecated knobs desugar onto the same rule type with a kind mask (counterDropLabels becomes a counters-only * rule). Rules are sorted by specificity — exact beats prefix, longer prefix beats shorter, ties break to the later entry, and a desugared legacy knob sits below an equally specific customization so migrating a field takes effect. Lookups (Exposed, labelIndices, BucketsFor) walk the sorted rules and take the most specific match.

SetHistogramBuckets. A narrower entry point that resolves the bucket string and registers the histogram families only. Two callers use it: config validation, which wants the parse error and nothing else, and test setup, which needs histograms scrapeable while leaving counter label sets still open. Production goes through Configure.

Label projection (LabeledCounter, LabeledHistogram). Each wrapper stores the full canonical label schema alongside the projected subset the policy keeps for that family. Call sites always pass the full schema in schema order; the wrapper silently projects before forwarding to the underlying prometheus.CounterVec/HistogramVec. A length mismatch panics immediately. ActiveLabelValues returns the projected subset so handle-caches key on the effective (post-projection) labels, preventing duplicate series. Gauges are never projected — collapsing gauge series would report whichever writer wrote last rather than a coarser number. [telemetry/labeled_histogram.go:L117-130]

Handle caching. CounterHandle, GaugeHandle, and ObserverHandle in telemetry/handles.go cache children in sync.Maps keyed by {Vec pointer, '\x1f'-joined label values}. This avoids per-observation map lookup and mutex contention inside the Prometheus library. [telemetry/handles.go:L59-105]

Idle-series eviction. The health tracker's rotateMetricsLoop fires a sweep every 10 rotation ticks (default 30 s/tick = every 5 minutes). The sweep deletes upsMetrics and ntwMetrics entries not accessed in the last 30 minutes (DefaultIdleEvictionAfter), then calls DeleteLabelValues on erpc_upstream_request_duration_seconds and erpc_rate_limits_total. Cordoned entries and wildcard ("*") rollups are never evicted. All other high-cardinality series are NOT covered — cardinality is bounded only by distinct label tuples seen over the process lifetime. [health/tracker.go:L557-667]

Metrics HTTP server. erpc.Init starts a minimal http.Server bound to :%d (port only, all interfaces) serving the Prometheus handler at the root path. This means every URL path on the metrics port returns identical Prometheus text output — GET /, GET /metrics, GET /health all work. No TLS, no auth, no gzip. Shutdown is graceful with a 5-second budget. The gatherer is wrapped by telemetry.Gatherer, which applies the policy's drop rules to families the manager does not register itself (the stock go_*/process_*/promhttp_* collectors); with no customizations configured it hands the default gatherer straight through. [erpc/init.go:L184-215]

ERPC_NOMETRICS=1 env var. When set before process start, initflags.go:init() replaces prometheus.DefaultRegisterer and DefaultGatherer with a fresh empty registry. All subsequent registrations succeed but produce no-ops. The default go_*/process_* collectors are also dropped. [cmd/erpc/initflags.go:L22-28]

errorLabelMode global. common.ErrorSummary produces the error label value on every error-labeled metric. The in-code default before erpc.Init runs is verbose; erpc.Init flips it to compact (the config default). In compact mode it emits short stable codes: a plain StandardError → its Base().Code string (e.g. "ErrEndpointCapacityExceeded"); for ErrFailsafeRetryExceeded, ErrUpstreamRequest, or ErrUpstreamRequestSkipped it appends the cause code (e.g. "ErrUpstreamRequest/ErrEndpointTransportFailure"); for an ErrJsonRpcExceptionInternal cause it further appends the numeric code (e.g. "ErrUpstreamRequest/ErrJsonRpcExceptionInternal/-32000"). In verbose mode it passes the full message chain through cleanUpMessage (strips newlines and long hex strings), potentially including block numbers or IP addresses — each unique value creates a new Prometheus series. [common/errors.go:L17-114]

networkAlias resolver. At startup, erpc.Init installs a NetworkAliasResolver callback mapping raw EVM chain IDs to human-readable aliases so components using numeric chain IDs (e.g. gRPC cache connector) emit matching network label values. [erpc/init.go:L95-110]


Config schema

All fields are under metrics. in the root YAML config. Source: common/config.go:L2899-3016 (struct), common/defaults.go:L908-931 (defaults), common/validation.go:L139-171 (validation).

FieldTypeDefaultBehavior / footguns
metrics.enabled*booltrue in production; nil (disabled) under go testWhether to start the /metrics HTTP server. Metrics are still registered and counted when false — they just are not scraped. Source: common/defaults.go:L909-911
metrics.port*int4001TCP port for the metrics server. Required when enabled=true; erpc.Init aborts if nil. Source: common/defaults.go:L918-920
metrics.hostV4*string"0.0.0.0"Defined and defaulted but not used — the server binds ":%d" (all interfaces). Setting this to 127.0.0.1 does NOT restrict to loopback. Use firewall rules instead. Source: erpc/init.go:L189
metrics.hostV6*string"[::]"Same caveat as hostV4 — defined but not used in the bind address. Source: common/defaults.go:L915-917
metrics.listenV4*boolnilDefined in struct but never read in production code. Dead config field.
metrics.listenV6*boolnilDefined in struct but never read. Dead config field.
metrics.errorLabelModestring"compact"Controls the error label on all error-labeled metrics. "compact" → short stable codes (bounded cardinality, recommended). "verbose" → full human messages including block numbers and IP addresses (unbounded cardinality risk). Must be "", "compact", or "verbose". Source: common/defaults.go:L921-923
metrics.histogramBucketsstring"" → [0.05, 0.5, 5, 30]Comma-separated float64 bucket boundaries for every histogram that does not declare its own buckets. Empty → use defaults. Parsed and sorted by ParseHistogramBuckets. Invalid float → warning + fallback to defaults. Source: telemetry/metrics.go:L943-948
metrics.customizations[]objectnil (nothing customized)The one knob for shaping /metrics. Each entry has a subject, an optional action, an optional labels list, and optional buckets. Startup-only — every field is resolved before the first registration, so a change needs a restart. Source: telemetry/policy.go
metrics.customizations[].subjectstringrequiredSelects metric families: an exact name ("upstream_request_total"), a prefix ending in * ("consensus_*"), or * for every family. The erpc_ prefix is optional. Stock collectors are named in full ("go_goroutines", "process_cpu_seconds_total", "promhttp_metric_handler_requests_total") and obey the same rules, so subject: "*" with action: drop drops them too. * is only legal as the final character, and the same subject may not appear twice.
metrics.customizations[].action"keep" | "drop"none (exposure untouched)drop removes the matched families from /metrics; keep spares them from a broader drop. Omit it to customize labels or buckets without touching exposure. A dropped eRPC family is never registered, so it costs no series, no collection time and no scrape bytes; a dropped stock collector is filtered out of the scrape response instead, which shrinks the page but not collection.
metrics.customizations[].labels[]objectnil (full label set)A list of {subject, action} entries projecting the matched families' label sets, with the same precedence rules applied to label names — agent_*: drop then agent_name: keep drops the group and spares the one label. Counters and histograms are projected; gauges are not.
metrics.customizations[].buckets[]float64nilReplaces the bucket boundaries of the matched histograms, overriding both metrics.histogramBuckets and what the metric declares in code. Must be strictly increasing. Ignored (with a startup warning) if an exact subject names something that is not a histogram.
metrics.histogramDropLabels[]stringnilDeprecated — use a customizations entry with subject: "*" and a labels list. Still honored: desugared into an every-histogram label drop on the same rule engine. Source: common/config.go:L2928-2944
metrics.histogramLabelOverridesmap[string][]stringnilDeprecated — use a customizations entry with the exact subject and a labels list keeping what that metric needs. Source: common/config.go:L2928-2944
metrics.counterDropLabels[]stringnilDeprecated — same replacement as histogramDropLabels. Desugared into an every-counter label drop.
metrics.counterLabelOverridesmap[string][]stringnilDeprecated — same replacement as histogramLabelOverrides.

Precedence. Overlapping subjects resolve by specificity, not by list order: an exact family name beats a prefix, a longer prefix beats a shorter one, and two equally specific subjects break to the one written later. So drop consensus_* plus keep consensus_duration_seconds means the same thing whichever order it appears in, and appending a carve-out never requires reordering the list. The deprecated label knobs are desugared into the same rules but sit below an equally specific customizations entry, so migrating a field off them takes effect immediately.

Hardcoded server constants (not configurable):

  • Bind address: ":%d" — all interfaces, port only (erpc/init.go:L189)
  • Handler: the Prometheus handler registered as root /, over a gatherer wrapped by telemetry.Gatherer — every path returns full metrics output
  • Protocol: plain HTTP only (no TLS, no auth, no gzip)
  • ReadHeaderTimeout: 10 seconds
  • Graceful shutdown budget: 5 seconds

Histograms affected by metrics.histogramBuckets: erpc_upstream_request_duration_seconds, erpc_network_request_duration_seconds, erpc_consensus_duration_seconds, erpc_cache_set_success_duration_seconds, erpc_cache_set_error_duration_seconds, erpc_cache_get_success_hit_duration_seconds, erpc_cache_get_success_miss_duration_seconds, erpc_cache_get_error_duration_seconds.

Histograms NOT affected by metrics.histogramBuckets: all remaining histograms declare dedicated inline bucket arrays, and the global list deliberately leaves those alone. They are still reachable via a customizations entry with buckets, which overrides the compiled-in array.


Worked examples

1. Drop user globally but keep it on one histogram for user-level latency analysis. Useful when you need per-user latency breakdowns on network_request_duration_seconds but want to limit cardinality everywhere else:

metrics:
  customizations:
    # Fleet-wide: no per-user, no per-agent dimensions.
    - subject: "*"
      labels:
        - subject: user
          action: drop
        - subject: agent_name
          action: drop
    # …except on this one histogram, which a latency dashboard reads per user.
    - subject: network_request_duration_seconds
      labels:
        - subject: user
          action: keep

2. Tighter histogram buckets for a low-latency deployment (sub-100ms upstreams). The default [0.05, 0.5, 5, 30] buckets produce poor p95/p99 resolution when upstreams respond in under 50ms. Replacing them gives useful percentile data at real latencies:

metrics:
  histogramBuckets: "0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,3"

3. Disabling metrics during integration tests. erpc.Init defaults metrics.enabled to nil (disabled) under go test, but if you call Init with a partial config that doesn't go through SetDefaults, the server may try to start. Explicit override:

metrics:
  enabled: false

4. Keeping only the families a dashboard actually reads. The strongest cardinality lever available: a dropped family is never registered, so it costs no series, no collection time, and no scrape bytes. An allowlist is written explicitly — drop everything, then keep what you want — so nothing depends on which other keys are present. subject: "*" also covers the stock go_*/process_* collectors, so name them if you want to keep them:

metrics:
  customizations:
    - subject: "*"
      action: drop
    - subject: upstream_request_total
      action: keep
    - subject: upstream_request_errors_total
      action: keep
    - subject: network_request_duration_seconds
      action: keep
    - subject: "go_*"
      action: keep
    - subject: "process_*"
      action: keep

5. Keeping a subsystem but dropping its noisiest family. The exact subject is more specific than the prefix, so the carve-out does not require enumerating everything else — and the order of the two entries does not matter:

metrics:
  customizations:
    - subject: "consensus_*"
      action: keep
    - subject: consensus_responses_collected
      action: drop

6. Per-metric histogram buckets. metrics.histogramBuckets is one global list, which is the wrong resolution when two histograms measure different things. buckets on a customization overrides it — and the metric's own compiled-in buckets — for just the families the subject matches:

metrics:
  histogramBuckets: "0.05,0.5,5,30"
  customizations:
    # Sub-100ms upstreams need resolution the global list does not have.
    - subject: upstream_request_duration_seconds
      buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 1]
    # This one counts blocks, not seconds.
    - subject: network_evm_get_logs_range_requested
      buckets: [10, 100, 1000, 10000, 100000]

7. Switching to verbose error labels for a debugging session. When you need to trace a specific block-number error pattern in Grafana and short codes aren't enough — but remember to revert after, as each unique block number creates a new Prometheus series:

metrics:
  errorLabelMode: verbose

Behavioral invariants

  • No metric family is registered at package init. Every family — counters, gauges, histograms — becomes scrapeable only once telemetry.Configure runs from erpc.Init. A process that never calls Init (or calls it with a config whose metrics section is absent) exposes the histogram families only. [telemetry/manager.go]
  • Exposure is a startup decision. A family dropped by a customization is never registered, so it cannot be turned back on without a restart — and a family that was registered cannot be removed by a later Configure, because Prometheus keeps the frozen label-set hash for the registry's lifetime.
  • Metrics still count when metrics.enabled is false: enabled only controls whether the HTTP server starts. A drop customization is what actually prevents a family from existing.
  • A customization with no action never affects exposure. It customizes labels or buckets only, and it does not turn on the scrape-path filter that hides stock collectors.
  • A repeat Configure on the same registry leaves already-registered families untouched — their label sets are frozen, and accumulated values are preserved. Changing customizations or histogramBuckets requires a restart.
  • ERPC_NOMETRICS=1 swaps prometheus.DefaultRegisterer/DefaultGatherer for a fresh empty registry before Configure runs — all subsequent registrations succeed but produce no-ops; the stock go_*/process_* collectors are also dropped. [cmd/erpc/initflags.go:L22-28]
  • Idle sweep runs every 5 minutes (default); series not accessed in 30 minutes are evicted from upstream_request_duration_seconds and rate_limits_total via DeleteLabelValues. [health/tracker.go:L557-667]
  • The error label value is produced by common.ErrorSummary. Compact mode yields a bounded set of code-path identifiers; verbose mode can produce one series per unique block number or IP. [common/errors.go:L46-66]
  • erpc_rate_limiter_budget_decision_total is registered but has zero production call sites — always zero. [telemetry/metrics.go:L515-519]
  • erpc_network_hedge_delay_seconds is registered but never observed in production — always empty. [telemetry/metrics.go:L813]

Best practices

  • Use errorLabelMode: compact (the default) in production. Verbose mode can create one Prometheus series per unique block number or IP address in error messages — this is an unbounded cardinality hazard.
  • Drop user and agent_name with a subject: "*" customization when serving untrusted callers or managing many API keys. These labels are the leading cardinality source across the projected families. Re-add them with an exact subject on the one or two metrics a dashboard or billing pipeline actually reads.
  • Do not rely on hostV4/hostV6 to restrict scrape access — the server binds all interfaces regardless of those fields. Use firewall rules or network policy to limit who can reach port 4001.
  • Alert on erpc_network_failed_request_total{severity="critical"} and page on severity="warning". Severity is already classified by common.ClassifySeverity — no need to build your own error-code regexes.
  • Use erpc_rate_limits_total for rate-limit alerting. The bundled alert rule referencing erpc_upstream_request_self_rate_limited_total uses a stale metric name that no longer exists.
  • Monitor erpc_upstream_cordoned as an early-warning signal for upstream health. A non-zero value means eRPC has autonomously excluded that upstream; combine with erpc_upstream_cordon_event_total to track how often it happens.
  • For erpc_network_evm_block_range_requested_total, be aware the bucket label is unbounded. Consider a Prometheus metric_relabel_configs drop rule for the bucket label if long-running chains accumulate too many series.

Edge cases & gotchas

  1. hostV4/hostV6 are defined but unused. The bind address is always ":%d" (all interfaces). Firewall rules are the only way to restrict access to the metrics port. (erpc/init.go:L189)
  2. metrics.enabled is nil (disabled) under go test. Integration tests that call erpc.Init will not spin up a metrics server unless Enabled: true is set explicitly. (common/defaults.go:L909-911)
  3. Changing errorLabelMode does not retroactively relabel existing series. Counter Vec children are created lazily on first WithLabelValues. Any series created before erpc.Init sets the mode carry verbose labels; subsequent series carry compact labels. Both sets coexist in Prometheus until the verbose series naturally expire.
  4. Label-set, bucket and exposure changes need a restart. Prometheus keeps a family's label-set hash for the registry's lifetime, so a second Configure leaves already-registered families exactly as they are — a changed customizations list or histogramBuckets is silently not applied to them. (telemetry/manager.go)
  5. A subject that matches nothing is only a warning. A typo'd family name is not a validation error — Init logs the unmatched subjects, quoting the text you wrote, and starts anyway. Check the startup log for subjects match no known metric family after editing the list. Subjects naming a stock collector are excluded from the check, since those are registered outside the manager and are never in KnownFamilies.
  6. A rule aimed at a family that cannot honor it is also only a warning. buckets on a counter, or labels on a plain counter or gauge whose label set is fixed in code, is silently ignored; Init warns only when an exact subject names such a family, so a sweeping subject: "*" does not produce one warning per family. Look for rules do not apply to the family they name.
  7. Third-party collectors outside go_*/process_*/promhttp_* cannot be addressed. Any other name is normalized by prepending erpc_, so a collector registered by a dependency under some other prefix is unreachable from customizations — subject: "*", action: drop still removes it (via the scrape filter), but no keep can name it back. (telemetry/policy.go)
  8. Idle sweep evicts only upstream_request_duration_seconds and rate_limits_total. All other high-cardinality histograms and counters (network_request_duration_seconds, upstream_request_total, etc.) are never DeleteLabelValues'd — they grow monotonically. For method-flood scenarios drop user and agent_name with a subject: "*" customization.
  9. erpc_cors_requests_total project label receives r.URL.Path, not the project ID. Series will have values like /myproject/evm/1. Dashboard queries on CORS metrics must match the path-style value, not a bare project ID. (erpc/http_server.go:L1020)
  10. erpc_ristretto_cache_current_cost requires memory.emitMetrics: true. Without this flag the gauge is registered and appears in /metrics but always returns 0. It is the only metric in the system requiring an explicit opt-in to be meaningful. (data/memory.go:L71)
  11. erpc_upstream_cordoned category label is the cordon scope (method string or "*"), not a request-category label. Do not join this category with category from request-accounting metrics. (health/tracker.go:L458-465)
  12. erpc_upstream_cordoned vendor label is "n/a" when no vendor is configured. Dashboard JOINs between cordon metrics and upstream request metrics must handle the "n/a" value for unvendored upstreams. (upstream/upstream.go:L275-286)
  13. erpc_auth_failed_total always has strategy="database". Only the database auth strategy emits this counter. For other strategies, monitor failures via erpc_network_failed_request_total{error=~"ErrAuthUnauthorized.*"}. (auth/strategy_database.go:L467-472)
  14. erpc_upstream_attempt_outcome_total is_hedge/is_retry are literal "true"/"false" strings. Use {is_hedge="true"} in PromQL, not {is_hedge="1"}. (upstream/upstream.go:L80-85)
  15. erpc_network_evm_block_range_requested_total bucket label is unbounded. When tip is known, ComputeBlockHeatmapBucket produces human-readable relative labels ("TIP", "L100k", "100k-200k", etc.). This counter is not covered by the idle sweep — cardinality grows permanently. (erpc/block_heatmap.go:L87-175)
  16. erpc_selection_* metrics carry a method label. Untrusted callers injecting hundreds of distinct method names create unbounded selection-gauge series. Consider a Prometheus relabeling drop rule for the method label in untrusted deployments.
  17. erpc_upstream_block_head_large_rollback is silent for rollbacks ≤ 1024 blocks, and sticky above them. DefaultToleratedBlockHeadRollback = 1024 is the threshold. The gauge is Set to the rollback size and is never reset, so > 0 means "this process saw a rollback at some point", not "a rollback is happening now", and rate()/increase() are meaningless on it. Alert on changes(...[window]), split by the finality label. (architecture/evm/evm_state_poller.go:L27)
  18. Cordoned upstreams are never swept. A cordoned tracker entry persists in memory — and the erpc_upstream_cordoned{...} = 1 gauge persists in /metrics — until the upstream is uncordoned and subsequently goes idle for idleEvictionAfter. (health/tracker.go:L603-608)
  19. erpc_network_dynamic_block_time_milliseconds is bounded 10ms–120s. Fast chains (block time below 10ms) or stuck chains (no blocks for over 2 minutes) report the floor or ceiling value, not the true EMA. (health/tracker.go:L1456)
  20. Two bundled alert rules reference stale metric names. erpc_upstream_request_self_rate_limited_total and erpc_network_request_self_rate_limited_total no longer exist — use erpc_rate_limits_total as the replacement. (monitoring/prometheus/alert.rules:L1)
  21. The bundled Prometheus scrape target includes REPLACE_SERVICE_ENDPOINT_HERE and REPLACE_SERVICE_PORT_HERE placeholders. These are Railway deployment stubs that cause a parse error in strict Prometheus configs. Remove or replace both before using monitoring/prometheus/prometheus.yml in production.
  22. ParseHistogramBuckets silently sorts the input. If metrics.histogramBuckets is provided out of order (e.g. "5,0.5,0.05,30") the values are sorted automatically and no warning is emitted. The resulting bucket set is valid, but operators relying on a specific insertion order will not get what they typed. [telemetry/metrics.go:L1133-1151]
  23. erpc_upstream_response_size_bytes is intentionally tight-cardinality. Its label set is project, network, category, finality — no user or upstream — because per-user response-size breakdown is not operationally actionable. If you need per-user latency analysis, retain user on erpc_upstream_request_duration_seconds with an exact-subject labels: [{subject: user, action: keep}] customization instead of not dropping it globally. [telemetry/metrics.go:L1125-1130]
  24. erpc_upstream_request_duration_seconds has two high-cardinality labels: user and composite. The composite label carries the composite-request type (e.g. "logs-split-on-error", "query-logs-shim") and, combined with user, can produce many series in high-traffic deployments. Both are candidates for a labels: [{subject: ..., action: drop}] customization; the idle sweep (DefaultIdleEvictionAfter = 30 min) provides a safety valve. [health/tracker.go:L333-358]
  25. ErrEndpointServerSideException HTTP status code is not exposed as a Prometheus label. When an upstream returns an HTTP 5xx that does not match a known pattern, eRPC wraps it in ErrEndpointServerSideException and echoes the upstream's status code back to the caller via ErrorStatusCode(). The raw numeric code surfaces in erpc_upstream_request_errors_total{error="ErrEndpointServerSideException"} in compact mode (no code number) or in the full message in verbose mode, but no label carries it. For debugging, use OTel traces: SetTraceSpanError attaches attribute.String("error.code", stdErr.CodeChain()) and the serialised StandardError JSON to the span — though originalStatusCode is a private field and is not in the JSON output. The only way to retrieve the exact upstream HTTP status is from structured logs (logged at DEBUG level before wrapping). [common/errors.go:L1951-1979]

Full metrics catalog

All metric names carry the erpc_ prefix. Full definitions: telemetry/metrics.go:L11-1131.

Area 1: Upstream request accounting

MetricTypeLabelsWhen it fires
erpc_upstream_request_totalcounterproject, vendor, network, upstream, category, attempt, composite, finality, user, agent_nameEach actual attempt sent to an upstream. composite ∈ "none", "logs-split-on-error", "logs-split-proactive", "trace-filter-split-on-error", "trace-filter-split-proactive", "query-blocks-shim", "query-transactions-shim", "query-logs-shim", "query-traces-shim", "query-transfers-shim".
erpc_upstream_request_errors_totalcounterproject, vendor, network, upstream, category, error, severity, composite, finality, user, agent_nameUpstream attempt returned an error. severity ∈ "critical", "warning", "info" via common.ClassifySeverity. Also incremented with severity=info for block-availability-gated skips.
erpc_upstream_request_skipped_totalcounterproject, vendor, network, upstream, category, finality, user, agent_nameUpstream pre-forward checks decided to skip.
erpc_upstream_request_missing_data_error_totalcounterproject, vendor, network, upstream, category, finality, user, agent_nameUpstream returned missing-data / not-synced error.
erpc_upstream_request_empty_response_totalcounterproject, vendor, network, upstream, category, finality, user, agent_nameUpstream returned an emptyish response.
erpc_upstream_wrong_empty_response_totalcounterproject, vendor, network, upstream, category, finality, user, agent_nameUpstream returned empty while consensus determined others had data.
erpc_upstream_selection_totalcounterproject, network, upstream, category, reason, finalityUpstream picked for an attempt. reason ∈ "primary", "retry", "hedge", "consensus_slot", "sweep". "sweep" = the upstream was picked as part of runUpstreamSweep — the try-all-upstreams iteration used in the non-consensus path when the primary fails and retries loop through remaining upstreams in sequence.
erpc_upstream_credit_units_totalcounterproject, network, upstream, vendor, category, finalityVendor credit units accrued by upstream attempts (Alchemy CUs, QuickNode credits, dRPC CUs). Summed across retries/hedges/consensus slots; cache hits and never-dialed attempts cost 0. Values are each vendor's own units — not money, not comparable across vendors. No user label (cardinality); per-user cost is in the X-ERPC-Credits response header and traces. Vendorless upstreams are excluded.
erpc_upstream_attempt_outcome_totalcounterproject, network, upstream, category, outcome, is_hedge, is_retry, finalityTerminal attempt outcome. outcome ∈ "success", "empty", "transport_error", "server_error", "client_error", "rate_limited", "missing_data", "exec_revert", "block_unavailable", "breaker_open", "cancelled", "timeout", "skipped". is_hedge/is_retry are literal "true"/"false".
erpc_upstream_request_duration_secondsLabeledHistogramproject, vendor, network, upstream, category, composite, finality, userDuration of each upstream attempt. Idle series swept every ~30 min. user is a cardinality candidate for a label drop customization.
erpc_upstream_response_size_bytesLabeledHistogramproject, network, category, finalityDecoded post-gzip result-body byte count. Tight label set by design (no user). Buckets: 4096, 65536, 1048576, 16777216, 104857600.

Area 2: Network request accounting

MetricTypeLabelsWhen it fires
erpc_network_request_received_totalcounterproject, network, category, finality, user, agent_nameRequest received for a network.
erpc_network_failed_request_totalcounterproject, network, category, attempt, error, severity, finality, user, agent_nameRequest failed at network/project level. severity ∈ "critical", "warning", "info". Page on critical, ticket on warning, ignore info.
erpc_network_successful_request_totalcounterproject, network, vendor, upstream, category, attempt, finality, emptyish, user, agent_nameRequest succeeded. emptyish ∈ "true"/"false" — true means the response was empty-ish (null, empty array).
erpc_network_multiplexed_request_totalcounterproject, network, category, finality, user, agent_nameRequest de-duplicated into an identical in-flight request.
erpc_network_static_response_served_totalcounterproject, network, categoryServed from a configured static response; no upstream touched.
erpc_network_timeout_fired_totalcounterproject, network, category, finality, scopeTimeout policy killed a request. scope ∈ "network", "upstream". Suppressed when retry-exhausted error wins.
erpc_network_retry_attempt_totalcounterproject, network, category, reason, finalityNetwork-scope retry. reason ∈ "empty_result", "pending_tx", "retryable_error", "block_unavailable", "missing_data".
erpc_network_request_duration_secondsLabeledHistogramproject, network, vendor, upstream, category, finality, userEnd-to-end network request duration. vendor/upstream = "<error>" on failure.
erpc_network_data_unavailable_wait_secondsLabeledHistogramproject, network, category, reason, finalityWall-clock catch-up delay before data-not-yet-available retry. Buckets: 0.1, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64 s.
erpc_network_timeout_duration_secondsLabeledHistogramproject, network, category, finalityComputed dynamic timeout per request. Buckets: 0.05, 0.1, 0.3, 0.5, 1, 3, 5, 10, 30 s.

Area 3: Block number and tip tracking

On SVM networks these carry slots, not block heights. The names were coined for EVM and are reused verbatim by the SVM state poller, so erpc_upstream_latest_block_number is the upstream's processed slot, erpc_upstream_finalized_block_number its finalized (rooted) slot, and the *_lag gauges count slots. That distinction is not cosmetic: a skipped Solana slot advances the slot counter but not the block height, so slot and height are different counters. The rollback threshold on SVM is DefaultToleratedSlotRollback = 1024 slots. erpc_upstream_stale_latest_block_total fires when a getSlot answer is corrected to the highest known slot for its commitment. The poller emits no *_polled_total, no erpc_network_dynamic_block_time_milliseconds, no served-tip gauges, and no metric at all for shred-insert (ingestion) lag — see SVM slot tracking & health.

MetricTypeLabelsWhen it fires
erpc_upstream_latest_block_numbergaugeproject, vendor, network, upstreamUpstream's latest block advanced.
erpc_upstream_finalized_block_numbergaugeproject, vendor, network, upstreamUpstream's finalized block advanced.
erpc_upstream_block_head_laggaugeproject, vendor, network, upstreamBlocks behind the freshest upstream.
erpc_upstream_finalization_laggaugeproject, vendor, network, upstreamFinalized blocks behind the freshest upstream.
erpc_upstream_block_head_large_rollbackgaugeproject, vendor, network, upstream, finalityBlock head rolled back by more than DefaultToleratedBlockHeadRollback = 1024 blocks. Gauge value = absolute delta of the rollback. finality is the head axis that rolled back (latest or finalized), not a DataFinalityState. Sticky: never reset, so a non-zero value means "this process saw a rollback at some point", not "a rollback is happening now".
erpc_upstream_latest_block_polled_totalcounterproject, vendor, network, upstreamState poller proactively polled latest block.
erpc_upstream_finalized_block_polled_totalcounterproject, vendor, network, upstreamState poller proactively polled finalized block.
erpc_upstream_stale_latest_block_totalcounterproject, vendor, network, upstream, categoryUpstream returned a stale latest block vs. others.
erpc_upstream_stale_finalized_block_totalcounterproject, vendor, network, upstreamUpstream returned a stale finalized block vs. others.
erpc_upstream_stale_upper_bound_totalcounterproject, vendor, network, upstream, category, confidenceRequest skipped: upstream latest block < requested upper bound. confidence ∈ "blockHead", "finalizedBlock".
erpc_upstream_stale_lower_bound_totalcounterproject, vendor, network, upstream, category, confidenceRequest skipped: requested lower bound below upstream's available range. Same confidence values.
erpc_network_latest_block_timestamp_distance_secondsgaugeproject, network, originnow − latest block timestamp. origin ∈ "evm_state_poller", "network_response".
erpc_network_dynamic_block_time_millisecondsgaugeproject, networkEMA block-time estimate (α=0.1, min 3 samples). Returns 0 until 3 samples. Bounded 10ms–120s.
erpc_network_served_tip_block_numbergaugeproject, network, lane, axisServed-tip pick per axis (latest/finalized). lane="all" = network-wide; a named lane = the use-upstream group for that lane.
erpc_network_served_tip_lag_blocksgaugeproject, network, lane, axisLag of served tip behind freshest velocity-eligible upstream. Absent in default MAX mode.
erpc_network_served_tip_upstream_excluded_totalcounterproject, network, upstream, axis, reasonUpstream excluded from served-tip pick. reason ∈ "velocity", "outlier". Absent in MAX mode.

Area 4: Cache connector freshness

MetricTypeLabelsWhen it fires
erpc_cache_connector_earliest_block_numbergaugeconnector, networkgRPC cache connector availability poll — earliest available block.
erpc_cache_connector_latest_block_numbergaugeconnector, networkSame poll — latest block.
erpc_cache_connector_finalized_block_numbergaugeconnector, networkSame poll — finalized block.
erpc_cache_connector_earliest_block_timestamp_secondsgaugeconnector, networkSame poll — earliest block unix timestamp.
erpc_cache_connector_latest_block_timestamp_secondsgaugeconnector, networkSame poll — latest block unix timestamp.
erpc_cache_connector_finalized_block_timestamp_secondsgaugeconnector, networkSame poll — finalized block unix timestamp.

Area 5: Cache operations (JSON-RPC cache)

MetricTypeLabelsWhen it fires
erpc_cache_get_success_hit_totalcounterproject, network, category, connector, policy, ttlCache get hit.
erpc_cache_get_success_miss_totalcounterproject, network, category, connector, policy, ttlCache get miss.
erpc_cache_get_error_totalcounterproject, network, category, connector, policy, ttl, errorCache get errored.
erpc_cache_get_skipped_totalcounterproject, network, categoryCache get skipped — no matching policy.
erpc_cache_get_age_guard_reject_totalcounterproject, network, method, connector, policy, ttlCached item rejected: block-timestamp age exceeded policy TTL.
erpc_cache_set_success_totalcounterproject, network, category, connector, policy, ttlCache set succeeded.
erpc_cache_set_error_totalcounterproject, network, category, connector, policy, ttl, errorCache set errored.
erpc_cache_set_skipped_totalcounterproject, network, category, connector, policy, ttlCache set skipped by policy.
erpc_cache_set_original_bytes_totalcounterproject, network, category, connector, policy, ttlUncompressed bytes written on cache set.
erpc_cache_set_compressed_bytes_totalcounterproject, network, category, connector, policy, ttlCompressed bytes written on cache set.
erpc_cache_get_success_hit_duration_secondsLabeledHistogramproject, network, category, connector, policy, ttlCache get hit duration.
erpc_cache_get_success_miss_duration_secondsLabeledHistogramproject, network, category, connector, policy, ttlCache get miss duration.
erpc_cache_get_error_duration_secondsLabeledHistogramproject, network, category, connector, policy, ttl, errorCache get error duration.
erpc_cache_set_success_duration_secondsLabeledHistogramproject, network, category, connector, policy, ttlCache set success duration.
erpc_cache_set_error_duration_secondsLabeledHistogramproject, network, category, connector, policy, ttl, errorCache set error duration.
erpc_ristretto_cache_current_costgaugeconnectorRistretto (memory connector) current total cost. Requires memory.emitMetrics: true — always 0 otherwise.
erpc_ristretto_cache_sets_failed_totalcounterconnectorRistretto set dropped / rejected.

Area 6: Rate limiting

MetricTypeLabelsWhen it fires
erpc_rate_limits_totalcounterproject, network, vendor, upstream, category, finality, user, agent_name, budget, scope, auth, originUnified rate-limit event. scope="remote", origin="upstream", budget="<remote>" = upstream 429 passthrough. For local budget denials, scope = ScopeString() of the rule: a comma-joined set of enabled scope flags (e.g. "user", "network", "ip", or combinations like "user,network"); origin = empty string. Idle series deleted by health-tracker sweep.
erpc_rate_limiter_budget_max_countgaugebudget, method, scopeBudget's allowed req/s, set on rule creation or auto-tuner update. scope = ScopeString() comma-joined flags (e.g. "user,network").
erpc_rate_limiter_budget_decision_totalcounterproject, network, category, finality, user, agent_name, budget, method, scope, decisionDEPRECATED / DORMANT — registered but zero production call sites. Always 0.
erpc_rate_limiter_failopen_totalcounterproject, network, user, agent_name, budget, category, reasonRate limiter failed open. reason ∈ "admission_full", "limit_timeout".
erpc_rate_limiter_remote_inflightgaugebudgetConcurrent in-flight remote (e.g. Redis) DoLimit calls per budget.
erpc_rate_limiter_remote_admission_shedded_totalcounterbudgetRemote check fail-opened because admission semaphore full (load shed).
erpc_rate_limiter_remote_duration_secondsLabeledHistogrambudget, resultRemote rate-limit check duration. result ∈ "ok", "over_limit", "fail_open". Buckets: 0.001–5 s.

Area 7: Upstream health (cordon, circuit-breaker, probing)

MetricTypeLabelsWhen it fires
erpc_upstream_cordonedgaugeproject, vendor, network, upstream, category, reason1 on cordon, 0 on uncordon. category = method string or "*" (wholesale). NOT the standard request-category label. vendor = "n/a" when unvendored.
erpc_upstream_cordon_event_totalcounterproject, network, upstream, actionAdmin cordon/uncordon. action ∈ "cordon", "uncordon".
erpc_upstream_cordon_duration_secondshistogramproject, network, upstreamSeconds spent cordoned, observed on each uncordon. Buckets: 1–86400 s.
erpc_upstream_breaker_state_change_totalcounterproject, upstream, transitionCircuit-breaker state transition. transition ∈ "closed_to_open", "half_open_to_open", "half_open_to_closed", "open_to_half_open".
erpc_selection_probe_requests_totalcounternetwork, upstream, methodProbe-mirror request fired at an excluded upstream.
erpc_selection_probe_errors_totalcounternetwork, upstream, method, reasonProbe request errored. reason ∈ "timeout", "throttled", "auth", "skipped", "error". "skipped" = upstream intentionally rejected; "error" = other failures.
erpc_selection_probe_skipped_totalcounternetwork, reasonProbe candidate skipped pre-fire. reason ∈ "write_method", "opt_out", "sampled_out", "max_concurrent", "no_method".
erpc_selection_probe_dropped_totalcounternetwork, reasonProbe-bus publish dropped — per-network feed channel full.

Area 8: Selection policy

MetricTypeLabelsWhen it fires
erpc_selection_positiongaugeproject, network, method, upstreamTick output: 0=primary, 1+=runner-up, −1=excluded.
erpc_selection_scoregaugeproject, network, method, upstreamPer-upstream sortByScore score (lower=better). Absent for upstreams that bypassed scoring.
erpc_selection_eligible_upstreamsgaugeproject, network, methodCount of upstreams returned by most recent tick.
erpc_selection_rejection_totalcounterproject, network, method, upstream, stepTick rejected upstream at a std-lib step.
erpc_selection_exclusion_totalcounterproject, network, method, upstream, reasonExclusion event. reason = leaf-predicate slug.
erpc_selection_shadow_exclusion_totalcounterproject, network, method, upstream, reasonshadowExcludeIf would-have-excluded; upstream stays in rotation.
erpc_selection_excluded_secondsgaugeproject, network, method, upstreamWall-clock seconds continuously excluded. 0 when in rotation.
erpc_selection_readmit_totalcounterproject, network, method, upstreamExcluded → in-list transition.
erpc_selection_primary_switch_totalcounterproject, network, method, from, toPrimary upstream changed between ticks.
erpc_selection_sticky_hold_totalcounterproject, network, method, upstreamstickyPrimary held a primary that would otherwise flip.
erpc_selection_eval_duration_secondshistogramproject, network, methodPer-tick selection-policy eval latency. Buckets: 0.0005–1 s.
erpc_selection_eval_errors_totalcounterproject, network, method, kindEval failure. kind ∈ "timeout", "throw", "invalid_return", "fallback_default".
erpc_selection_readmit_age_secondshistogramproject, network, methodnow − excludedSince at readmit. Buckets: 1–3600 s.

Area 9: Consensus

MetricTypeLabelsWhen it fires
erpc_consensus_totalcounterproject, network, category, outcome, finality, user, agent_nameConsensus round completed. outcome ∈ "success", "consensus_on_error", "dispute", "low_participants", "generic_error", "caller_abandoned".
erpc_consensus_misbehavior_detected_totalcounterproject, network, upstream, category, finality, response_type, larger_than_consensus, user, agent_nameUpstream returned different data than consensus. response_type ∈ "NonEmpty", "Empty", "ConsensusError", "InfrastructureError".
erpc_consensus_upstream_punished_totalcounterproject, network, upstream, user, agent_nameUpstream punished after misbehavior threshold.
erpc_consensus_short_circuit_totalcounterproject, network, category, reason, finality, user, agent_nameRound short-circuited. reason ∈ "sendrawtx_first_success", "consensus_error_threshold", "unassailable_lead".
erpc_consensus_wait_capped_totalcounterproject, network, category, trigger, finality, user, agent_nameRound resolved early by maxWaitOnResult/maxWaitOnEmpty. trigger ∈ "result", "empty".
erpc_consensus_errors_totalcounterproject, network, category, error, finality, user, agent_nameConsensus-level error by type.
erpc_consensus_upstream_errors_totalcounterproject, network, upstream, category, finality, response_type, error_code, user, agent_nameParticipant upstream errored during consensus. response_type same values as misbehavior_detected.
erpc_consensus_panics_totalcounterproject, network, category, finality, user, agent_namePanic recovered inside consensus.
erpc_consensus_cancellations_totalcounterproject, network, category, phase, finality, user, agent_nameContext cancelled during consensus. phase ∈ "before_execution", "after_execution", "caller_abandoned".
erpc_consensus_duration_secondsLabeledHistogramproject, network, category, outcome, finality, user, agent_nameConsensus round duration.
erpc_consensus_responses_collectedLabeledHistogramproject, network, category, vendors, short_circuited, finality, user, agent_nameResponses collected before decision. vendors = comma-joined sorted vendor names. short_circuited ∈ "true"/"false". Linear buckets 1–10.
erpc_consensus_agreement_countLabeledHistogramproject, network, category, finality, user, agent_nameUpstreams agreeing on most common result. Linear buckets 1–10.

Area 10: Hedging

MetricTypeLabelsWhen it fires
erpc_network_hedged_request_totalcounterproject, network, upstream, category, attempt, finality, user, agent_nameHedged request fired.
erpc_network_hedge_discards_totalcounterproject, network, upstream, category, attempt, hedge, finality, user, agent_nameHedged request discarded (wasted work; losing leg cancelled).
erpc_network_hedge_winner_totalcounterproject, network, upstream, category, finalityHedge race won by the upstream whose response was kept.
erpc_network_hedge_delay_secondsLabeledHistogramproject, network, category, finalityDORMANT — registered but no production observe site.

Area 11: EVM method-specific

MetricTypeLabelsWhen it fires
erpc_network_evm_get_logs_split_success_totalcounterproject, network, user, agent_nameA split eth_getLogs sub-request succeeded.
erpc_network_evm_get_logs_split_failure_totalcounterproject, network, user, agent_nameA split eth_getLogs sub-request failed.
erpc_network_evm_get_logs_forced_splits_totalcounterproject, network, dimension, user, agent_nameeth_getLogs forcibly split. dimension ∈ "block_range", "addresses", "topics0" (topics[0] OR-array only).
erpc_network_evm_trace_filter_split_success_totalcounterproject, network, method, user, agent_nameSplit trace_filter/arbtrace_filter sub-request succeeded.
erpc_network_evm_trace_filter_split_failure_totalcounterproject, network, method, user, agent_nameSplit trace_filter/arbtrace_filter sub-request failed.
erpc_network_evm_trace_filter_forced_splits_totalcounterproject, network, method, dimension, user, agent_nametrace_filter split. dimension ∈ "block_range", "from_address", "to_address".
erpc_network_evm_block_range_requested_totalcounterproject, network, vendor, upstream, category, user, finality, bucket, sizeBlock-range heatmap. bucket uses tip-relative labels ("TIP", "L100k", "100k-200k", etc.) when tip is known; falls back to static 100000-block aligned labels otherwise. Not covered by idle sweep — cardinality is unbounded.
erpc_network_evm_get_logs_range_requestedLabeledHistogramproject, network, category, user, finalityeth_getLogs requested block-range size (observed value = toBlock − fromBlock). Buckets: 1, 10, 100, 500, 1000, 5000, 10000, 30000.
erpc_network_evm_trace_filter_range_requestedLabeledHistogramproject, network, method, user, finalitytrace_filter/arbtrace_filter requested block-range size. Same buckets.

Area 12: gRPC BDS resilience

MetricTypeLabelsWhen it fires
erpc_grpc_bds_hard_timeout_totalcounterproject, upstream, methodBDS gRPC call hit the bounded-wait hard timeout (20 s, hard-coded). Non-zero rate indicates H2 stream wedging; the watchdog then force-replaces the connection.
erpc_grpc_bds_conn_replacements_totalcounterproject, upstreamBDS pool connection force-closed by stuck-call watchdog.

Area 13: CORS

MetricTypeLabelsWhen it fires
erpc_cors_requests_totalcounterproject, originRequest carrying an Origin header. Note: project label = r.URL.Path (e.g. /myproject/evm/1), not the project ID.
erpc_cors_preflight_requests_totalcounterproject, originAllowed-origin OPTIONS preflight. Same project mislabeling.
erpc_cors_disallowed_origin_totalcounterproject, originRequest from disallowed origin. Same project mislabeling.

Area 14: Shadow testing

MetricTypeLabelsWhen it fires
erpc_shadow_response_identical_totalcounterproject, vendor, network, upstream, categoryShadow upstream response identical to expected.
erpc_shadow_response_mismatch_totalcounterproject, vendor, network, upstream, category, finality, emptyish, largerShadow response differs. emptyish ∈ "true"/"false" (shadow was empty-ish). larger ∈ "true"/"false" (shadow body larger than primary).
erpc_shadow_response_error_totalcounterproject, vendor, network, upstream, category, errorShadow upstream request errored.

Area 15: Auth

MetricTypeLabelsWhen it fires
erpc_auth_failed_totalcounterproject, network, strategy, reason, agent_nameFailed authentication attempt. strategy is always "database" — only the database auth strategy emits this counter.

Area 16: Panics

MetricTypeLabelsWhen it fires
erpc_unexpected_panic_totalcounterscope, extra, errorRecovered panic. scope ∈ "request-handler", "final-error-writer", "top-level-handler", "timeout-handler", "validate-pattern", "redis-pubsub", "shared-state-registry", "matcher".

Histogram bucket sets

Bucket setValuesApplies to
DefaultHistogramBuckets (overridable via metrics.histogramBuckets)0.05, 0.5, 5, 30 supstream_request_duration_seconds, network_request_duration_seconds, consensus_duration_seconds, all 5 cache duration histograms (8 total)
EvmGetLogsRangeHistogramBuckets1, 10, 100, 500, 1000, 5000, 10000, 30000 blocksnetwork_evm_get_logs_range_requested, network_evm_trace_filter_range_requested
CatchUpWaitHistogramBuckets0.1, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64 snetwork_data_unavailable_wait_seconds
hedge delay (hard-coded)0.01, 0.03, 0.05, 0.2, 0.3, 0.5, 0.7, 1, 3 snetwork_hedge_delay_seconds (dormant)
timeout duration (hard-coded)0.05, 0.1, 0.3, 0.5, 1, 3, 5, 10, 30 snetwork_timeout_duration_seconds
selection eval (hard-coded)0.0005, 0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1 sselection_eval_duration_seconds
readmit age (hard-coded)1, 5, 15, 30, 60, 120, 300, 600, 1800, 3600 sselection_readmit_age_seconds
cordon duration (hard-coded)1, 10, 60, 300, 900, 1800, 3600, 7200, 21600, 86400 supstream_cordon_duration_seconds
rate-limiter remote (hard-coded)0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 5 srate_limiter_remote_duration_seconds
response size (hard-coded)4096, 65536, 1048576, 16777216, 104857600 bytesupstream_response_size_bytes
consensus counts (linear)1, 2, 3, 4, 5, 6, 7, 8, 9, 10consensus_responses_collected, consensus_agreement_count

Only DefaultHistogramBuckets are configurable at runtime. All other bucket sets require a recompile.


Observability

The metrics endpoint: http://<host>:<metrics.port>/ (any path) — the Prometheus handler is mounted at the root, so every HTTP path on port 4001 returns the full Prometheus text exposition. Plain HTTP only, no auth, no path routing. Reference scrape config at monitoring/prometheus/prometheus.yml (opens in a new tab) (scrape interval 10 s, default target host.docker.internal:4001).

Stock collectors (present unless ERPC_NOMETRICS=1): go_* — Go runtime stats; process_* — OS process stats; promhttp_metric_handler_* — handler self-scrape stats.

Bundled alerting rules (monitoring/prometheus/alert.rules):

AlertExpressionThreshold
HighErrorRaterate(erpc_upstream_request_errors_total[5m]) / rate(erpc_upstream_request_total[5m]) > 0.05 by upstream5% error rate for 5 min
SlowRequestshistogram_quantile(0.95, rate(erpc_upstream_request_duration_seconds_bucket[5m])) > 1 by upstreamp95 > 1 s for 5 min
HighRateLimitingreferences stale metric erpc_upstream_request_self_rate_limited_total — use erpc_rate_limits_total instead—
NetworkRateLimitingreferences stale metric erpc_network_request_self_rate_limited_total — use erpc_rate_limits_total instead—
HighRequestRaterate(erpc_upstream_request_total[5m]) by (upstream) > 1000>1000 rps per upstream
LowRequestRaterate(erpc_upstream_request_total[5m]) by (upstream) < 1<1 rps per upstream for 15 min

Metrics server log lines (all emitted by erpc/init.go):

LevelMessage
Info"starting metrics server on port: %d"
Error"error starting metrics server: %s"
Info"shutting down metrics server..."
Error"metrics server forced to shutdown: %s"
Info"metrics server stopped"
Error"failed to apply metrics configuration; no metric families are registered" — a malformed metrics.customizations entry. telemetry.Configure returns before registering anything, so /metrics serves only the stock go_/process_ collectors. Alert on this.
Warn"failed to apply metrics configuration; falling back to default histogram buckets" — an unparseable metrics.histogramBuckets float. Every family is still registered; only the bucket boundaries are wrong.
Info"metric customizations applied" (with customizations/exposed/total counts; only when metrics.customizations is set)
Warn"metrics.customizations subjects match no known metric family; check for typos"
Warn"some metrics.customizations rules do not apply to the family they name"

Both "failed to apply metrics configuration" lines are only reachable when erpc.Init is called as a library: the CLI rejects a malformed customization and a bad bucket list in MetricsConfig.Validate before Init runs. The wrapped error names the offending field either way.


Source code entry points

Related pages

  • Rate limiters — the erpc_rate_limits_total counter tracks every budget decision.
  • Failsafe: Hedge — erpc_network_hedged_request_total and erpc_network_hedge_winner_total are the hedge observability surface.
  • Failsafe: Retry — erpc_network_retry_attempt_total with its reason label tracks retry behavior.
  • Selection policies — the erpc_selection_* family tracks tick-by-tick upstream ranking.
  • Cache — erpc_cache_get_* and erpc_cache_set_* families cover all cache connector activity.
  • Auth — erpc_auth_failed_total for database-strategy failures.