Documentation
¶
Overview ¶
Package metrics provides HTTP request instrumentation interfaces and an in-memory implementation. Production deployments wire NewProviderCollector against a kernel/observability/metrics.Provider (backed by adapters/prometheus or adapters/otel); InMemoryCollector is retained for dev / tests that want an observable collector without reaching for a Provider.
Package metrics provides HTTP request instrumentation interfaces and an in-memory implementation. Production deployments should use an adapter (e.g., adapters/prometheus) that implements the Collector interface.
Index ¶
- Constants
- Variables
- func ConfigEventMiddleware() outbox.SubscriptionMiddleware
- func ConfigEventOwnerValidator(sub outbox.Subscription) error
- func RecordConfigEventProcess(ctx context.Context, collector ConfigEventCollector, ...)
- func WrapConfigEventSubscriber(collector ConfigEventCollector, sub outbox.Subscription, ...) outbox.SubscriberHandler
- type BodyLimitRejectionKey
- type CellLabel
- type Collector
- type ConfigEventCollector
- type ConfigEventProcessReason
- type ConfigStaleCipherCollector
- type EventRouterCollector
- func (c *EventRouterCollector) DecSubscriptionActive(ctx context.Context, cellID string)
- func (c *EventRouterCollector) IncSubscriptionActive(ctx context.Context, cellID string)
- func (c *EventRouterCollector) ObserveReadyWait(ctx context.Context, cellID string, d time.Duration)
- func (c *EventRouterCollector) RecordRuntimeError(ctx context.Context, cellID, topic string, ...)
- func (c *EventRouterCollector) RecordSetupError(ctx context.Context, cellID, topic, reason string)
- type EventbusCacheCollector
- type GRPCCollector
- type GRPCRequestKey
- type IdempotencyCollector
- type InMemoryCollector
- func (c *InMemoryCollector) Handler() http.Handler
- func (c *InMemoryCollector) RecordBodyLimitRejection(_ context.Context, cell CellLabel, route string)
- func (c *InMemoryCollector) RecordRequest(_ context.Context, cell CellLabel, method, route string, status int, ...)
- func (c *InMemoryCollector) Snapshot() Snapshot
- type InMemoryGRPCCollector
- type NoopConfigEventCollector
- type NoopConfigStaleCipherCollector
- type NoopEventbusCacheCollector
- type OutboxPendingDepthCollector
- type OutboxRejectCollector
- type ProviderCollectorConfig
- type RequestKey
- type SagaCollector
- func (c *SagaCollector) ObserveDrive(ctx context.Context, definitionID string, result executor.DriveResult)
- func (c *SagaCollector) ObserveHeartbeatFailure(ctx context.Context, _ idutil.SafeID, _ idutil.SafeID, ...)
- func (c *SagaCollector) ObserveLeaderSkip(ctx context.Context, definitionID string, reason executor.LeaderSkipReason)
- func (c *SagaCollector) ObserveOutcome(ctx context.Context, _ idutil.SafeID, _ idutil.SafeID, definitionID, _ string, ...)
- func (c *SagaCollector) ObserveRetry(ctx context.Context, _ idutil.SafeID, _ idutil.SafeID, ...)
- func (c *SagaCollector) ObserveTick(ctx context.Context, result executor.TickResult)
- type SessionCacheCollector
- type ShutdownCollector
- type Snapshot
Constants ¶
const ( // ShutdownPhaseCounterName counts entries into each named shutdown phase. // Labels: phase = readiness_flip | http_drain | lifo_teardown | closed. // SRE use: detect stuck shutdowns by comparing phase entry counts across // instances; a missing "closed" entry pinpoints where the hang occurred. // // Note: This is a Counter (not a Gauge). To check which phase a pod is stuck // in, operators must compare the delta between successive phase counters; a // phase counter that fails to increment while earlier phases have incremented // indicates the pod is stuck in the previous phase. ShutdownPhaseCounterName = "bootstrap_shutdown_phase_entries_total" // ShutdownPhaseDurationName records per-phase wall-clock latency. // Labels: phase = readiness_flip | http_drain | lifo_teardown | total. // SRE use: P99 histogram in Grafana reveals which phase dominates // shutdown latency. ShutdownPhaseDurationName = "bootstrap_shutdown_phase_duration_seconds" // ShutdownTotalCounterName counts completed shutdowns by outcome. // Labels: outcome ∈ {success, timeout, teardown_error, signal_error}. // - success : clean, user-initiated shutdown // - timeout : shutCtx expired during readiness flip or LIFO teardown // - teardown_error: at least one teardown returned non-nil (no timeout) // - signal_error : shutdown triggered by HTTP/worker/router failure, // teardown itself was clean // SRE use: alert on timeout / teardown_error; signal_error rate reveals // how often shutdown is triggered by component failures vs. human action. // ref: Kubernetes pod termination (success/failure/timeout tri-state). ShutdownTotalCounterName = "bootstrap_shutdown_total" )
Metric names for shutdown observability. Names are bare semantic identifiers (no namespace prefix). The final fully-qualified Prometheus name is assembled by the Provider's Namespace field (e.g. "gocell") via prom.BuildFQName(Namespace, Subsystem, Name), matching the canonical pattern used by kernel/outbox relay_collector.go and runtime/observability metrics. Provider configuration lives in cmd/corebundle/metrics.go (Namespace: "gocell").
const ( ShutdownPhaseReadinessFlip = "readiness_flip" // ShutdownPhaseHTTPDrain covers shutdown stage 2 — the explicit transport // intake drain that runs before LIFO teardown. As of #1148 this stage drains // BOTH the HTTP and gRPC listeners (they share one budget), so this single // label aggregates both transports' drain duration. Splitting gRPC into its // own phase label is deferred to PR-9 (#1152, observability parity); until // then a `phase="http_drain"` spike may be HTTP or gRPC. Per-transport error // attribution is preserved in the joined error (teardown_http_drain / // teardown_grpc_drain), just not in this metric label. ShutdownPhaseHTTPDrain = "http_drain" ShutdownPhaseLIFOTeardown = "lifo_teardown" ShutdownPhaseClosed = "closed" ShutdownPhaseTotal = "total" )
Phase label values for ShutdownPhaseCounterName.
const RuntimeCellSentinel = "_runtime"
RuntimeCellSentinel is the framework "owner" used as the cell label for requests that do not belong to any cell-owned namespace. It is the single, transport-neutral source of the "_runtime" sentinel: both the HTTP middleware (runtime/http/middleware.Metrics / CellAttribution / body-limit rejection) and the gRPC metrics interceptor read this constant, so the value is declared exactly once. See observability.md "HTTP Metrics cell Label".
Variables ¶
var DefaultDurationBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
DefaultDurationBuckets are the histogram buckets previously hardcoded in adapters/prometheus.Collector. Matching them keeps existing Grafana dashboards valid after the migration to the provider-neutral surface.
Functions ¶
func ConfigEventMiddleware ¶
func ConfigEventMiddleware() outbox.SubscriptionMiddleware
ConfigEventMiddleware installs config-event owner metadata into the handler context so that RecordConfigEventProcess can retrieve it inside the EntryHandler. Settlement observation is handled at the SubscriberHandler layer by WrapConfigEventSubscriber; this middleware only injects owner ctx.
The non-config-prefix fast path (isConfigEventSubscription == false) is legitimate: non-config subscriptions and audit/command topics pass through without instrumentation. Config subscriptions missing owner metadata are intercepted at registration time by ConfigEventOwnerValidator and never reach this middleware.
func ConfigEventOwnerValidator ¶
func ConfigEventOwnerValidator(sub outbox.Subscription) error
ConfigEventOwnerValidator enforces that any subscription on an event.config.* topic carries owner metadata (CellID + SliceID) so config-event observability cannot be silently dropped at runtime. Composition roots register this with EventRouter.AddSubscriptionValidator.
ref: kratos middleware/recovery — fail at registration boundary, not at delivery time, so misconfigurations surface during bootstrap.
func RecordConfigEventProcess ¶
func RecordConfigEventProcess(ctx context.Context, collector ConfigEventCollector, reason ConfigEventProcessReason)
RecordConfigEventProcess records a handler/process reason using the owner metadata installed by ConfigEventMiddleware.
func WrapConfigEventSubscriber ¶
func WrapConfigEventSubscriber( collector ConfigEventCollector, sub outbox.Subscription, next outbox.SubscriberHandler, ) outbox.SubscriberHandler
WrapConfigEventSubscriber wraps a SubscriberHandler to append a settlement observer that records final broker disposition via ConfigEventCollector. This is the SubscriberHandler-layer counterpart to ConfigEventMiddleware (which handles the EntryHandler layer).
Non-config subscriptions take the fast path and return next unchanged. ConfigEventCollector nil is replaced with NoopConfigEventCollector.
Types ¶
type BodyLimitRejectionKey ¶
BodyLimitRejectionKey identifies one body-limit rejection metric series.
type CellLabel ¶
type CellLabel struct {
// contains filtered or unexported fields
}
CellLabel is the sealed, validated cell dimension for a metric series. It is the only type the Collector / GRPCCollector record methods accept for the `cell` label.
Sealed construction (Hard, same shape as kernel/outbox.Entry): the single field is unexported and the sole exported constructor is ResolveCellLabel, so constructing a non-zero CellLabel outside this package is a compile error. Passing a raw, unvalidated cell-id string to a collector is therefore unexpressible — every cell label that reaches a collector has been checked against the assembly's closed cell-id set, and an out-of-set or absent cell degrades to RuntimeCellSentinel rather than polluting the platform SLO series.
This is the M12b (#1093) runtime defense-in-depth, deliberately LAYERED with — not contradicting — the M12a build-time closed-set gate (composition.Builder.Build):
- M12a hard-rejects an out-of-set cell id at BUILD time: a misconfigured assembly must fail fast before serving ("越界 cellID 硬拒、不降级").
- M12b degrades an out-of-set cell id to the sentinel at the RUNTIME metric write point: a live request must not panic over a metric label, so the one label degrades rather than crashing or polluting the series ("→ _runtime").
"不降级" governs the build path; "→ sentinel" governs the runtime label path. See .claude/rules/gocell/observability.md "HTTP Metrics cell Label".
func ResolveCellLabel ¶
ResolveCellLabel is the SOLE constructor of a non-zero CellLabel. It reads the owning cell id written into ctx by the listener-root CellAttribution middleware and returns it ONLY when present AND a member of valid — the assembly's closed cell-id set, threaded in by the router via WithCellIDClosedSet. An absent cell id, an empty one, or one not in valid (including an empty/nil valid set, which is never a member) yields the zero CellLabel, whose String() renders as RuntimeCellSentinel.
Test code that needs a CellLabel without a real request context should use metricstest.Label(id) (runtime/observability/metrics/metricstest), which routes through this funnel.
func (CellLabel) String ¶
String returns the metric label value: the validated cell id, or RuntimeCellSentinel for the zero value (absent / out-of-set / framework path). The zero-value-is-sentinel rule means a CellLabel{} produced by any path — including the unavoidable Go zero value — can never emit an empty or forged cell label.
type Collector ¶
type Collector interface {
// RecordRequest records a completed HTTP request with the given labels.
// route is the route pattern (e.g. "/api/v1/users/{id}"), not the actual
// request path. Using route patterns prevents metric cardinality explosion.
//
// cell is the coarse owner dimension for the request, carried as the sealed
// [CellLabel] whose only constructor is [ResolveCellLabel]. The collector
// never infers it from assembly, config, route path, tenant, slice, or
// contract metadata — ResolveCellLabel resolves it from request context and
// the assembly closed set, degrading framework-owned paths
// (healthz/readyz/metrics, unmatched 404s, listeners with no business
// RouteGroup, out-of-set cells) to RuntimeCellSentinel ("_runtime").
RecordRequest(ctx context.Context, cell CellLabel, method, route string, status int, durationSeconds float64)
// RecordBodyLimitRejection increments the body-limit rejection counter for
// the given cell and route. It is called only on the Content-Length
// fast-path reject (r.ContentLength > maxBytes before the request body is
// read).
//
// Streaming overruns (MaxBytesReader kick-in, after the body has started
// being read) are handler-dependent: the handler receives
// *http.MaxBytesError when it reads the body, and if it propagates the
// error through pkg/httputil.WriteError, the framework maps it to a 413
// response captured in http_requests_total{status="413"} via RecordRequest.
// Framework-generated handlers follow this path; custom handlers that do not
// call httputil.WriteError will not produce a 413 in http_requests_total.
//
// cell follows the same semantics as RecordRequest: the sealed [CellLabel]
// from [ResolveCellLabel] (validated cell id or RuntimeCellSentinel).
// route is the low-cardinality route pattern from RouteFor.
RecordBodyLimitRejection(ctx context.Context, cell CellLabel, route string)
}
Collector records HTTP request metrics.
func NewProviderCollector ¶
func NewProviderCollector(p kernelmetrics.Provider, cfg ProviderCollectorConfig) (Collector, error)
NewProviderCollector builds an HTTP Collector that records through a kernel-level metrics.Provider (Prometheus-backed, OTel-backed, Nop, …).
ref: kernel/observability/metrics — provider neutrality pattern. ref: pre-migration adapters/prometheus/collector.go — metric names and labels preserved so operators do not need to re-write their dashboards.
type ConfigEventCollector ¶
type ConfigEventCollector interface {
RecordEventProcess(ctx context.Context, cellID, sliceID string, reason ConfigEventProcessReason)
RecordEventSettlement(ctx context.Context, cellID, sliceID, disposition string, result outbox.SettlementResult)
}
ConfigEventCollector records config event process and settlement metrics.
func NewProviderConfigEventCollector ¶
func NewProviderConfigEventCollector(p kernelmetrics.Provider) (ConfigEventCollector, error)
NewProviderConfigEventCollector registers config-event consumer metrics on p. The Prometheus provider namespace supplies the "gocell_" fqName prefix.
type ConfigEventProcessReason ¶
type ConfigEventProcessReason string
ConfigEventProcessReason is the low-cardinality handler/process taxonomy for config event consumers. It is intentionally separate from broker settlement.
const ( ConfigEventProcessReasonAck ConfigEventProcessReason = "ack" ConfigEventProcessReasonStale ConfigEventProcessReason = "stale" ConfigEventProcessReasonPermanentError ConfigEventProcessReason = "permanent_error" // ConfigEventProcessReasonNoTenant is recorded when tenant.FromContext returns // an error (no tenant in consumer context). The event is Ack-ed as // retrying cannot supply a tenant that was absent at delivery time. ConfigEventProcessReasonNoTenant ConfigEventProcessReason = "no_tenant" // ConfigEventProcessReasonTransient is recorded when the config getter // returns a transient (non-permanent, non-404) error so the entry is // Requeued for retry. ConfigEventProcessReasonTransient ConfigEventProcessReason = "transient" )
type ConfigStaleCipherCollector ¶
ConfigStaleCipherCollector records reads of config values that are encrypted with a non-current key version (M3 stale-key observability).
func NewProviderConfigStaleCipherCollector ¶
func NewProviderConfigStaleCipherCollector(p kernelmetrics.Provider) (ConfigStaleCipherCollector, error)
NewProviderConfigStaleCipherCollector registers the config stale-cipher counter on p. The Prometheus provider namespace supplies the "gocell_" fqName prefix, so Name "config_stale_cipher_total" yields the wire metric "gocell_config_stale_cipher_total" — byte-identical to the prior raw-prometheus counter that lived in cmd/corebundle (configStaleCipherOpts). The metric is unlabeled (a single global count), so RecordStaleCipher increments the one series via the empty label set.
type EventRouterCollector ¶
type EventRouterCollector struct {
// contains filtered or unexported fields
}
EventRouterCollector registers event-router lifecycle metrics:
- event_router_subscriptions_active{cell} (Gauge): active subscription count
- event_router_setup_errors_total{cell,topic,reason} (Counter): setup-phase failures (Phase 1-3)
- event_router_ready_wait_seconds{cell} (Histogram): time waited for Ready signal
- event_router_runtime_errors_total{cell,topic,reason} (Counter): runtime-phase faults (Phase 4)
Histogram buckets for ready_wait_seconds are: 0.001, 0.01, 0.1, 0.5, 1, 5, 30 seconds. Ready usually completes in <100ms; bootstrap timeout is 30s, so the upper bound covers the full expected range.
PR #593 review fix-up (P2#3): the setup vs runtime metrics partition the router lifecycle by phase, NOT by error variety. event_router_setup_errors_total owns Phase 1–3 failures; event_router_runtime_errors_total owns Phase 4 faults. The reason label set is therefore disjoint across the two metrics:
- event_router_setup_errors_total reasons: "setup_error": Subscriber.Setup failed (Phase 1) "panic": Subscribe goroutine panicked (Phase 2) "ready_timeout": Ready not signaled within timeout (Phase 3) "subscribe_failure": SubscribeEntry failed before Running() (Phase 3)
- event_router_runtime_errors_total reasons: "runtime_fault": any fault detected in Phase 4 (after Running())
ref: Watermill router metrics middleware — subscription lifecycle counters and gauges matching the router_messages_processed_total / router_handler_active pattern.
func NewEventRouterCollector ¶
func NewEventRouterCollector(p kernelmetrics.Provider) (*EventRouterCollector, error)
NewEventRouterCollector registers all three event-router lifecycle metrics on the given provider. On partial failure, already-registered metrics are rolled back LIFO before returning the error.
func (*EventRouterCollector) DecSubscriptionActive ¶
func (c *EventRouterCollector) DecSubscriptionActive(ctx context.Context, cellID string)
DecSubscriptionActive decrements the active subscription gauge for cellID.
func (*EventRouterCollector) IncSubscriptionActive ¶
func (c *EventRouterCollector) IncSubscriptionActive(ctx context.Context, cellID string)
IncSubscriptionActive increments the active subscription gauge for cellID.
func (*EventRouterCollector) ObserveReadyWait ¶
func (c *EventRouterCollector) ObserveReadyWait(ctx context.Context, cellID string, d time.Duration)
ObserveReadyWait records the duration waited for the Ready signal on cellID.
func (*EventRouterCollector) RecordRuntimeError ¶
func (c *EventRouterCollector) RecordRuntimeError(ctx context.Context, cellID, topic string, reason eventrouter.RuntimeErrorReason)
RecordRuntimeError increments the runtime error counter for the given cell, topic, and reason. The reason argument is drawn from the closed set defined by the eventrouter package constants:
- eventrouter.RuntimeErrorReasonRuntimeFault ("runtime_fault"): any fault detected in Phase 4
func (*EventRouterCollector) RecordSetupError ¶
func (c *EventRouterCollector) RecordSetupError(ctx context.Context, cellID, topic, reason string)
RecordSetupError increments the setup error counter for the given cell, topic and reason. The reason argument is drawn from the closed set defined by the eventrouter package constants:
- eventrouter.SetupErrorReasonSetupError ("setup_error"): Subscriber.Setup failed (Phase 1)
- eventrouter.SetupErrorReasonPanic ("panic"): subscription goroutine panicked (Phase 2)
- eventrouter.SetupErrorReasonReadyTimeout ("ready_timeout"): Ready not signaled within timeout (Phase 3)
- eventrouter.SetupErrorReasonSubscribeFailure ("subscribe_failure"): SubscribeEntry failed before Running() (Phase 3)
type EventbusCacheCollector ¶
type EventbusCacheCollector interface {
RecordTombstoneEvicted(ctx context.Context, cellID, sliceID string)
}
EventbusCacheCollector records eventbus subscriber-cache lifecycle metrics.
func NewProviderEventbusCacheCollector ¶
func NewProviderEventbusCacheCollector(p kernelmetrics.Provider) (EventbusCacheCollector, error)
NewProviderEventbusCacheCollector registers eventbus-cache metrics on p. The Prometheus provider namespace supplies the "gocell_" fqName prefix.
type GRPCCollector ¶
type GRPCCollector interface {
RecordRPC(ctx context.Context, cell CellLabel, method, code string, durationSeconds float64)
}
GRPCCollector records gRPC unary server request metrics. It mirrors the HTTP Collector but emits a distinct metric family (grpc_server_*) with a gRPC-shaped label set: method (the full RPC method, e.g. "/pkg.Service/Method"), code (the gRPC status code name, e.g. "OK" / "Internal"), and cell (the coarse owner dimension, RuntimeCellSentinel until cell attribution is wired for gRPC).
The method is named RecordRPC (not RecordRequest like the HTTP Collector) because the gRPC signature is intentionally distinct — code (a gRPC status code name) replaces the HTTP status int + route; the two interfaces are not unified.
cell is the sealed CellLabel from ResolveCellLabel; the collector never infers it. gRPC cell attribution is not yet wired, so the interceptor passes ResolveCellLabel(ctx, nil), which resolves to RuntimeCellSentinel ("_runtime") until #1383 threads a real closed set.
func NewGRPCProviderCollector ¶
func NewGRPCProviderCollector(p kernelmetrics.Provider, cfg ProviderCollectorConfig) (GRPCCollector, error)
NewGRPCProviderCollector builds a GRPCCollector that records through a kernel-level metrics.Provider (Prometheus-backed, OTel-backed, Nop, …). DurationBuckets default to DefaultDurationBuckets, shared with the HTTP collector so dashboards use a uniform bucket layout across transports. The default's finest bucket is 5ms; intra-mesh RPCs that cluster below that can inject finer buckets via ProviderCollectorConfig.DurationBuckets.
type GRPCRequestKey ¶
GRPCRequestKey identifies one gRPC request metric series. Method holds the full RPC method ("/pkg.Service/Method"); its cardinality is bounded because gRPC methods are a registration-time enumerated set (unlike an HTTP path, which is collapsed to a route template to bound cardinality — gRPC needs no such templating).
type IdempotencyCollector ¶
type IdempotencyCollector struct {
// contains filtered or unexported fields
}
IdempotencyCollector records idempotency_requests_total{cell,state} — one increment per terminal HTTP idempotency decision made by the idempotency middleware. It implements idempotency.MetricsObserver.
Label semantics:
{cell}: coarse owner dimension read from ctx via kernel/ctxkeys.CellID (written by the router root CellAttribution middleware). Falls back to RuntimeCellSentinel ("_runtime") for framework paths or requests that do not match any registered RouteGroup. The resolution is identical to the one used by the HTTP metrics middleware for http_requests_total{cell}.
{state}: terminal idempotency decision, one of the six idempotency.RequestState constants. The value set is frozen by archtest IDEMPOTENCY-REQUESTS-STATE-LABEL-VALUES-FROZEN-01 (A1 freezes the constant set; A2 forbids inline literals or RequestState("x") conversions from reaching the label, requiring every value to flow from one of the declared constants). The string-typed-enum funnel mirrors executor.TickResult / DriveResult / LeaderSkipReason and reconcile resultLabel.
State relationship: StateAcquired ⊇ StateOversize. An acquired request whose response body exceeds the buffer limit emits both StateAcquired and StateOversize; the ratio oversize/acquired is the uncacheable-response rate.
Cardinality: 6 state values × bounded cell set = tiny worst-case series count, well within the metrics provider cap of 2000. No per-request or per-key dimension is exposed (keys are SHA-256 hashed for logs only).
Caller contract: to disable metric emission, pass nil to the middleware's idempotency.WithMetrics option — it silently skips a nil/typed-nil observer, so the middleware runs unmetered. Never pass a nil *IdempotencyCollector to ObserveRequest directly — it will dereference a nil CounterVec and panic.
func NewIdempotencyCollector ¶
func NewIdempotencyCollector(p kernelmetrics.Provider) (*IdempotencyCollector, error)
NewIdempotencyCollector registers idempotency_requests_total on p.
Unlike NewSagaCollector there is no constructor cellID parameter — the HTTP idempotency middleware is shared across cells on a listener, so cell is a per-record label read from ctx (the same approach used by NewProviderCollector and OutboxRejectCollector for http_requests_total).
Failure modes:
- p == nil → errcode.KindInvalid + ErrObservabilityConfigInvalid (no fallback)
- CounterVec registration error → wrapped with metric name
func (*IdempotencyCollector) ObserveRequest ¶
func (c *IdempotencyCollector) ObserveRequest(ctx context.Context, state idemhttp.RequestState)
ObserveRequest implements idempotency.MetricsObserver. It increments idempotency_requests_total{cell,state} once for each terminal idempotency decision.
cellID is read from ctx via kernel/ctxkeys.CellID (set by the router root CellAttribution middleware), falling back to RuntimeCellSentinel for framework-owned paths — the same resolution NewProviderCollector uses for http_requests_total{cell}.
The method is non-blocking and tolerates canceled contexts as required by MetricsObserver: CounterVec.Inc runs synchronously and never blocks on I/O.
type InMemoryCollector ¶
type InMemoryCollector struct {
// contains filtered or unexported fields
}
InMemoryCollector is a simple in-memory metrics collector for development and testing. It records request counts, cumulative duration, and body-limit rejection counts.
func NewInMemoryCollector ¶
func NewInMemoryCollector() *InMemoryCollector
NewInMemoryCollector creates an InMemoryCollector.
func (*InMemoryCollector) Handler ¶
func (c *InMemoryCollector) Handler() http.Handler
Handler returns an http.Handler that serves metrics as JSON. The response shape is:
{"data": {"requests": [...], "bodyLimitRejections": [...]}}
requests entries are sorted by cell→route→method→status. bodyLimitRejections entries are sorted by cell→route.
collector=nil fields are omitted (nil collector disables body-limit rejection recording; streaming 413s are still captured by the Metrics middleware via RecordRequest and appear in the requests array).
For Prometheus-compatible output, wire adapters/prometheus.NewMetricProvider into NewProviderCollector and serve the registry via promhttp.
func (*InMemoryCollector) RecordBodyLimitRejection ¶
func (c *InMemoryCollector) RecordBodyLimitRejection(_ context.Context, cell CellLabel, route string)
RecordBodyLimitRejection increments the body-limit rejection counter for the given cell and route. Mirrors the RLock fast-path + Lock lazy-init pattern of RecordRequest.
func (*InMemoryCollector) RecordRequest ¶
func (c *InMemoryCollector) RecordRequest(_ context.Context, cell CellLabel, method, route string, status int, durationSeconds float64)
RecordRequest records a completed HTTP request.
func (*InMemoryCollector) Snapshot ¶
func (c *InMemoryCollector) Snapshot() Snapshot
Snapshot returns a point-in-time copy of all metrics.
type InMemoryGRPCCollector ¶
type InMemoryGRPCCollector struct {
// contains filtered or unexported fields
}
InMemoryGRPCCollector is a simple in-memory GRPCCollector for development and testing. It records request counts and cumulative duration per label set.
func NewInMemoryGRPCCollector ¶
func NewInMemoryGRPCCollector() *InMemoryGRPCCollector
NewInMemoryGRPCCollector creates an InMemoryGRPCCollector.
func (*InMemoryGRPCCollector) Count ¶
func (c *InMemoryGRPCCollector) Count(cellID, method, code string) int64
Count returns the number of recorded requests for the given label set.
type NoopConfigEventCollector ¶
type NoopConfigEventCollector struct{}
NoopConfigEventCollector drops config event observations.
func (NoopConfigEventCollector) RecordEventProcess ¶
func (NoopConfigEventCollector) RecordEventProcess(_ context.Context, _, _ string, _ ConfigEventProcessReason)
func (NoopConfigEventCollector) RecordEventSettlement ¶
func (NoopConfigEventCollector) RecordEventSettlement(_ context.Context, _, _, _ string, _ outbox.SettlementResult)
type NoopConfigStaleCipherCollector ¶
type NoopConfigStaleCipherCollector struct{}
NoopConfigStaleCipherCollector drops stale-cipher observations.
func (NoopConfigStaleCipherCollector) RecordStaleCipher ¶
func (NoopConfigStaleCipherCollector) RecordStaleCipher(_ context.Context)
type NoopEventbusCacheCollector ¶
type NoopEventbusCacheCollector struct{}
NoopEventbusCacheCollector drops eventbus cache observations.
func (NoopEventbusCacheCollector) RecordTombstoneEvicted ¶
func (NoopEventbusCacheCollector) RecordTombstoneEvicted(_ context.Context, _, _ string)
type OutboxPendingDepthCollector ¶
type OutboxPendingDepthCollector struct {
// contains filtered or unexported fields
}
OutboxPendingDepthCollector registers outbox_pending_depth{cell} scoped to the owner cell supplied at construction. One collector per relay.
Metric registered:
- outbox_pending_depth{cell}: current ELIGIBLE pending outbox entry depth — rows with status=pending AND (next_retry_at IS NULL OR next_retry_at <= now()). Rows still in retry backoff are EXCLUDED, matching the ClaimPending eligibility predicate (Store.CountPending). Set on each Relay reclaim tick.
Sampling cadence is bound to Relay.ReclaimInterval (defaults to runtime/outbox.DefaultRelayReclaimInterval = 30s), not Prometheus scrape interval. SREs should treat this Gauge as "eligible depth at last reclaim tick", not real-time. A growing retry backlog will not inflate this value — sustained retry backlog must be diagnosed via the relay-side outcome counter outbox_relayed_total{outcome="dead"|"lost"} or the reclaim-budget readyz probe (outbox_consumer_rejected_total is a subscriber-side DLX counter, not a relay-side retry indicator).
PR #593 review fix-up (P2#5) aligned the help text and dashboards with the eligibility semantics introduced when Store.CountPending narrowed to claimable rows.
ref: Watermill router metrics middleware — gauge mirroring router queue depth.
func NewOutboxPendingDepthCollector ¶
func NewOutboxPendingDepthCollector(p kernelmetrics.Provider, cellID string) (*OutboxPendingDepthCollector, error)
NewOutboxPendingDepthCollector registers outbox_pending_depth on the given provider. Returns an error if registration fails.
cellID empty string returns error (no fallback to _runtime sentinel): this collector is per-cell and must be constructed with a real cell ID.
func (*OutboxPendingDepthCollector) ObservePendingDepth ¶
func (c *OutboxPendingDepthCollector) ObservePendingDepth(ctx context.Context, n int64)
ObservePendingDepth implements runtimeoutbox.PendingDepthObserver. Records the current pending outbox depth for the owner cell.
type OutboxRejectCollector ¶
type OutboxRejectCollector struct {
// contains filtered or unexported fields
}
OutboxRejectCollector registers outbox_consumer_rejected_total{cell,topic,reason}.
This collector is multi-cell-shared: the cell label flows from the ObserveReject call-site argument, NOT from construction time. There is therefore no cellID constructor argument. One instance covers all cells.
Metric registered:
- outbox_consumer_rejected_total{cell,topic,reason}: total terminal Reject dispositions observed by ConsumerBase. consumerGroup is excluded from the label set to keep time-series cardinality bounded; one cell may have multiple consumer groups sharing the same counter family.
ref: Watermill router metrics middleware — per-handler counters mirroring the router's router_messages_processed_total pattern.
func NewOutboxRejectCollector ¶
func NewOutboxRejectCollector(p kernelmetrics.Provider) (*OutboxRejectCollector, error)
NewOutboxRejectCollector registers outbox_consumer_rejected_total on the given provider. Returns an error if registration fails.
No cellID argument: cell label flows from ObserveReject's call-site argument.
func (*OutboxRejectCollector) ObserveReject ¶
func (c *OutboxRejectCollector) ObserveReject(ctx context.Context, cellID, topic, _, reason string)
ObserveReject implements kernel/outbox.ConsumerObserver. consumerGroup is received but not used as a label to keep cardinality bounded; the label set is {cell, topic, reason}.
Caller contract: topic MUST be a static contract identifier (ContractSpec.Topic). Passing runtime-derived values (message IDs, tenant IDs) causes unbounded cardinality.
type ProviderCollectorConfig ¶
type ProviderCollectorConfig struct {
// DurationBuckets overrides DefaultDurationBuckets; zero value uses defaults.
DurationBuckets []float64
}
ProviderCollectorConfig configures NewProviderCollector.
Cell identity is not part of this config — it is derived per request by router-owned root attribution and supplied through RecordRequest's cellID argument. See D1 HTTP-METRICS-LABEL-REALIGN.
type RequestKey ¶
RequestKey identifies one low-cardinality HTTP request metric series.
type SagaCollector ¶
type SagaCollector struct {
// contains filtered or unexported fields
}
SagaCollector registers the six saga metrics scoped to the owner cell supplied at construction. One collector per Coordinator (= per-cell). It satisfies executor.Observer, the saga-wide observability sink, so the same collector receives both Executor-emitted (step-level) and Coordinator-emitted (tick / drive / leader-skip) events.
Step-level metrics (Executor-emitted):
- saga_step_outcome_total{cell,definition_id,outcome}: total Execute calls terminated, labeled by the 5 Outcome variants (succeeded / failed / expired / canceled / lease_lost). step_name is intentionally NOT a label to keep the {step × outcome} Cartesian product bounded — outcome-level dashboards are aggregated across steps.
- saga_step_retry_total{cell,definition_id,step_name}: total retry attempts emitted between Execute runAttempt invocations (attempts ≥ 2). step_name IS a label here because retry-budget tuning is per-step.
- saga_heartbeat_failed_total{cell,reason}: total heartbeat tick failures, labeled by reason (infra_error / stale_lease). definition_id and step_name are excluded — heartbeat failure is an infra-level signal, not a per-step business metric.
Coordinator-level metrics (Coordinator-emitted):
- saga_tick_total{cell,result}: total ClaimPending cycles, labeled by result (claimed / empty / error). Loop liveness + claim activity; definition_id is not available pre-claim.
- saga_drive_total{cell,definition_id,result}: total driveOne completions, labeled by result (ok / error). Per-instance forward-progress throughput.
- saga_leader_elect_skip_total{cell,definition_id,reason}: total leader-elect skips, labeled by reason (contended / ctx_canceled / backend_error). reason="contended" sustained with drive{result="ok"}≈0 signals an instance stuck skipping; reason="backend_error" is the distlock lock-acquire failure rate. Replaces log-scraping the Debug-level skip path (#1109).
Cardinality discipline: definition_id × step_name is the worst case; all label dimensions are static enumeration sets (registered at compile time). result/reason add ≤3 values each over a bounded definition_id set. Expected upper bound: ≤ 300 active series per cell; metrics provider cap=2000 is the runtime tripwire.
ref: temporalio/sdk-go internal_task_handlers.go — server-emitted activity outcome / heartbeat-failure metric envelope. ref: itimofeev/go-saga (no native metrics — pattern adapted from outbox).
func NewSagaCollector ¶
func NewSagaCollector(p kernelmetrics.Provider, cellID string) (*SagaCollector, error)
NewSagaCollector registers the six saga counters on the given provider. cellID is the owner cell of the Coordinator wiring this collector; empty is an error (no fallback to _runtime sentinel — the saga Coordinator is always owned by exactly one cell).
Failure modes:
- p == nil → errcode.KindInvalid + ErrObservabilityConfigInvalid
- cellID == "" → errcode.KindInvalid + ErrObservabilityConfigInvalid
- any CounterVec registration error is wrapped with the metric name and rolls back the counters registered earlier in the sequence (atomic).
Caller contract: never pass a nil *SagaCollector — use NopObserver via WithObserver(nil) for explicit disable.
func (*SagaCollector) ObserveDrive ¶
func (c *SagaCollector) ObserveDrive(ctx context.Context, definitionID string, result executor.DriveResult)
ObserveDrive implements executor.Observer (Coordinator-emitted). Records one increment on saga_drive_total{cell,definition_id,result}.
func (*SagaCollector) ObserveHeartbeatFailure ¶
func (c *SagaCollector) ObserveHeartbeatFailure( ctx context.Context, _ idutil.SafeID, _ idutil.SafeID, reason executor.HeartbeatFailureReason, )
ObserveHeartbeatFailure implements executor.Observer. Records one increment on saga_heartbeat_failed_total{cell,reason}. instanceID and leaseID are carried for future audit/tracing use; they are intentionally NOT used as metric label dimensions (see ObserveOutcome).
func (*SagaCollector) ObserveLeaderSkip ¶
func (c *SagaCollector) ObserveLeaderSkip(ctx context.Context, definitionID string, reason executor.LeaderSkipReason)
ObserveLeaderSkip implements executor.Observer (Coordinator-emitted). Records one increment on saga_leader_elect_skip_total{cell,definition_id,reason}.
func (*SagaCollector) ObserveOutcome ¶
func (c *SagaCollector) ObserveOutcome( ctx context.Context, _ idutil.SafeID, _ idutil.SafeID, definitionID, _ string, outcome executor.Outcome, _ int, )
ObserveOutcome implements executor.Observer. Records one increment on saga_step_outcome_total{cell,definition_id,outcome}. instanceID and leaseID are carried for future audit/tracing use; they are intentionally NOT used as metric label dimensions to prevent cardinality explosion (per-instance high-cardinality values — see Observer godoc). attempts is also not labeled; it is carried into trace spans and slog by the executor itself.
func (*SagaCollector) ObserveRetry ¶
func (c *SagaCollector) ObserveRetry( ctx context.Context, _ idutil.SafeID, _ idutil.SafeID, definitionID, stepName string, )
ObserveRetry implements executor.Observer. Records one increment on saga_step_retry_total{cell,definition_id,step_name}. instanceID and leaseID are carried for future audit/tracing use; they are intentionally NOT used as metric label dimensions (see ObserveOutcome).
func (*SagaCollector) ObserveTick ¶
func (c *SagaCollector) ObserveTick(ctx context.Context, result executor.TickResult)
ObserveTick implements executor.Observer (Coordinator-emitted). Records one increment on saga_tick_total{cell,result}.
type SessionCacheCollector ¶
type SessionCacheCollector struct {
// contains filtered or unexported fields
}
SessionCacheCollector registers the four session-cache metrics scoped to the owner cell supplied at construction. One collector per CachingSessionStore (= per-cell; the session cache is always owned by exactly one cell — accesscore today). It is the Prometheus sink for the read-through Redis session cache (adapters/redis.CachingSessionStore), distinguishing cache-hit-rate erosion from partial Redis faults that previously were only visible in slog (#794).
- session_cache_hits_total{cell}: clean cache hits (a well-formed, valid entry was served from Redis without touching the inner store).
- session_cache_misses_total{cell}: cache misses (no entry in Redis; the inner store was consulted and the result lazily populated).
- session_cache_errors_total{cell}: read-path cache-access errors (Redis GET/SET failure, corrupt JSON, or a schema-invalid entry). All are fail-safe — they degrade to a miss and never propagate to the caller — so errors ⊆ misses holds — but their rate distinguishes "cache cold" from "Redis degraded". A single Get may emit both a miss and an error (e.g. a corrupt entry → evict-and-fall-through): the two series are orthogonal events, not mutually exclusive.
- session_cache_revoke_del_errors_total{cell}: post-commit single-session revoke cache-DEL failures (#796). A DISTINCT series from session_cache_errors_total — it is NOT a read-path miss (it fires on the committing goroutine after a durable revoke, outside any Get), so folding it into errors_total would break the read-path errors ⊆ misses math (every other error has a paired miss; a revoke DEL failure does not). A non-zero rate is a security-relevant signal: the logged-out session's invalidation degraded from near-zero to "expires at TTL" (stale ≤ TTL, the accepted backstop — see adapters/redis.CachingSessionStore godoc).
Cardinality: the sole label dimension is `cell`, a registration-time enumerated value (the owning cell ID), so total series = number of cells with a session cache enabled (1 today).
The cell label is baked at construction (mirrors SagaCollector) rather than resolved per-record via metrics.ResolveCellLabel: the session cache is a per-cell adapter primitive, not request-scoped framework middleware, so its owner is fixed at wiring time.
ref: prometheus/client_golang prometheus/counter.go (CounterVec). ref: runtime/observability/metrics/saga.go (per-cell collector pattern).
func NewSessionCacheCollector ¶
func NewSessionCacheCollector(p kernelmetrics.Provider, cellID string) (*SessionCacheCollector, error)
NewSessionCacheCollector registers the four session-cache counters on the given provider. cellID is the owner cell of the CachingSessionStore wiring this collector; empty is an error (no fallback to the _runtime sentinel — a per-cell cache always has exactly one owner).
Failure modes:
- p == nil → errcode.KindInvalid + ErrObservabilityConfigInvalid
- cellID == "" → errcode.KindInvalid + ErrObservabilityConfigInvalid
- any CounterVec registration error is wrapped with the metric name and rolls back the counters registered earlier in the sequence (atomic).
func (*SessionCacheCollector) RecordError ¶
func (c *SessionCacheCollector) RecordError(ctx context.Context)
RecordError increments session_cache_errors_total{cell} (read-path errors only; paired with a RecordMiss so errors ⊆ misses). Nil-receiver safe.
func (*SessionCacheCollector) RecordHit ¶
func (c *SessionCacheCollector) RecordHit(ctx context.Context)
RecordHit increments session_cache_hits_total{cell}. Nil-receiver safe so a disabled cache (no collector wired) is a no-op.
func (*SessionCacheCollector) RecordMiss ¶
func (c *SessionCacheCollector) RecordMiss(ctx context.Context)
RecordMiss increments session_cache_misses_total{cell}. Nil-receiver safe.
func (*SessionCacheCollector) RecordRevokeDelError ¶
func (c *SessionCacheCollector) RecordRevokeDelError(ctx context.Context)
RecordRevokeDelError increments session_cache_revoke_del_errors_total{cell} — a post-commit single-session revoke cache-DEL failure (#796). It is recorded only from the after-commit hook and is deliberately NOT a read-path error (no paired miss): folding it into RecordError would break the errors ⊆ misses contract. Nil-receiver safe.
type ShutdownCollector ¶
type ShutdownCollector struct {
// contains filtered or unexported fields
}
ShutdownCollector groups the three observability signals emitted during phase10OrchestrateShutdown. A nil *ShutdownCollector is safe to call — all methods are nil-receiver guards. This enables the "disabled without provider" contract without nil checks at every call site.
Design decision (plan option B): ShutdownCollector is created once in New() and stored on Bootstrap so metric instruments are registered against the Provider at construction time, matching the "register at start-up" pattern used by relay_collector.go and the kernel hook dispatcher.
func NewShutdownCollector ¶
func NewShutdownCollector(p kernelmetrics.Provider) (*ShutdownCollector, error)
NewShutdownCollector registers shutdown metrics on p and returns a *ShutdownCollector. A nil Provider returns a disabled metrics object, which leaves phase10 behavior unchanged without encoding success as nil data.
An error is returned only when the Provider itself fails to register a metric family (typically a duplicate name in the same registry). Callers treat this as fatal (consistent with relay_collector.go registration pattern).
ref: kernel/outbox.NewProviderRelayCollector — rollback-on-partial-failure pattern for metric registration.
func (*ShutdownCollector) CountOutcome ¶
func (m *ShutdownCollector) CountOutcome(ctx context.Context, outcome string)
CountOutcome increments the shutdown outcome counter. outcome must be one of "success", "timeout", "teardown_error", or "signal_error". No-op on nil receiver.
func (*ShutdownCollector) ObservePhaseDuration ¶
func (m *ShutdownCollector) ObservePhaseDuration(ctx context.Context, phase string, d time.Duration)
ObservePhaseDuration records the duration of a shutdown phase. No-op on nil receiver.
func (*ShutdownCollector) RecordPhaseEntry ¶
func (m *ShutdownCollector) RecordPhaseEntry(ctx context.Context, phase string)
RecordPhaseEntry increments the phase-entry counter for the given phase label. No-op on nil receiver.
type Snapshot ¶
type Snapshot struct {
RequestCounts map[RequestKey]int64
DurationSumsMs map[RequestKey]int64
BodyLimitRejections map[BodyLimitRejectionKey]int64
}
Snapshot is a point-in-time view of recorded metrics.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package metricstest provides a shared conformance suite for implementations of metrics.Collector.
|
Package metricstest provides a shared conformance suite for implementations of metrics.Collector. |