Documentation
¶
Overview ¶
Package obs is the library's instrumentation: metrics, traces and the provenance log.
It exists because every one of this library's degradations — a coalesce, a drop, a suppression, a rejection, an eviction — has to be observable, and the alternative to one package owning that is the catalogue living in whichever package last needed a counter. In all four of the systems this design was written against, the dominant production failure is a degradation with no signal; a degradation that is not observable is a defect here, not a tuning opportunity.
Nothing is computed when it is disabled ¶
Metrics, traces and logs are each disabled by leaving their provider nil, and a disabled configuration pays one predictable branch. The mechanism is a nil pointer with methods, not a no-op interface implementation: an interface would put an indirect call on the hot path and make the overhead budget a matter of the inliner's mood. Every method here begins by testing its receiver against nil, and calling any of them on a nil receiver is correct and free.
Cardinality ¶
No causal identifier is ever a metric label — not the session, the event, the patch or the transition. Those live in traces and in the provenance log, which carry the whole chain without multiplying a time series per connection. Event and fragment label values are bounded by registration. The origin source is not, because nothing registers an effect, so it is bounded at the metric: sixty-four distinct values, then a collapse to "other" with an overflow counter that says the collapse happened.
The provenance log is not the metrics path ¶
Provenance records are emitted by the session actor from the same values that construct the frame; the counters they are audited against are incremented by the framer and the transport. Different code, different sink. Coupling them would make the audit check a value against itself, which is the failure the audit exists to catch.
Index ¶
- Constants
- type Field
- type Logger
- func (l *Logger) Debug(ctx context.Context, msg string, fields ...Field)
- func (l *Logger) Enabled() bool
- func (l *Logger) Error(ctx context.Context, msg string, fields ...Field)
- func (l *Logger) Info(ctx context.Context, msg string, fields ...Field)
- func (l *Logger) Lifecycle(ctx context.Context, msg string, fields ...Field)
- func (l *Logger) Provenance(ctx context.Context, p Provenance)
- func (l *Logger) Warn(ctx context.Context, msg string, fields ...Field)
- type Metrics
- func (m *Metrics) ClientTelemetryDropped(ctx context.Context, reason string)
- func (m *Metrics) ClientTiming(ctx context.Context, morphMicros, applyMicros uint32)
- func (m *Metrics) ConnectionClosed(ctx context.Context, codeLabel string)
- func (m *Metrics) ConnectionOpened(ctx context.Context)
- func (m *Metrics) Effect(ctx context.Context, source, result string)
- func (m *Metrics) EffectOverran(ctx context.Context)
- func (m *Metrics) Enabled() bool
- func (m *Metrics) EncodeDuration(ctx context.Context, seconds float64)
- func (m *Metrics) EventReceived(ctx context.Context, name string)
- func (m *Metrics) EventRejected(ctx context.Context, reason string)
- func (m *Metrics) FrameReceived(ctx context.Context, kind string, bytes int)
- func (m *Metrics) FrameRejected(ctx context.Context, reason string)
- func (m *Metrics) FrameSent(ctx context.Context, kind string, bytes int)
- func (m *Metrics) Goroutines(ctx context.Context, delta int64)
- func (m *Metrics) MailboxDepth(ctx context.Context, depth int)
- func (m *Metrics) OutboundInvalid(ctx context.Context, kind string)
- func (m *Metrics) Panic(ctx context.Context, site string)
- func (m *Metrics) PatchCoalesced(ctx context.Context)
- func (m *Metrics) PatchesSent(ctx context.Context, op string, n int)
- func (m *Metrics) PatchesSuppressed(ctx context.Context, n int)
- func (m *Metrics) RenderDuration(ctx context.Context, seconds float64, fragment string)
- func (m *Metrics) ResyncRequest(ctx context.Context, result string, bytes int)
- func (m *Metrics) SendDuration(ctx context.Context, seconds float64)
- func (m *Metrics) SessionsActive(ctx context.Context, delta int64)
- func (m *Metrics) SlowClientEvent(ctx context.Context)
- func (m *Metrics) SourceLabel(ctx context.Context, source string) string
- func (m *Metrics) TrackedBytes(ctx context.Context, delta int64)
- func (m *Metrics) Transition(ctx context.Context, result string, seconds float64, event string)
- func (m *Metrics) WindowDepth(ctx context.Context, depth int)
- type Provenance
- type Span
- type SpanRef
- type Tracer
- func (t *Tracer) Enabled() bool
- func (t *Tracer) Start(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, Span)
- func (t *Tracer) StartChildOf(ctx context.Context, name string, parent SpanRef, attrs ...attribute.KeyValue) (context.Context, Span)
- func (t *Tracer) StartLinked(ctx context.Context, name string, to SpanRef, attrs ...attribute.KeyValue) (context.Context, Span)
Constants ¶
const ( InfoSampleThreshold = 100 InfoSampleRate = 100 )
InfoSampleThreshold is the records-per-second above which Info is sampled, and InfoSampleRate is the sampling. A record that survives sampling says so, because a sampled stream that does not admit it is a lie about volume.
const ( AttrSessionID = "gotthlive.session.id" AttrEventID = "gotthlive.event.id" AttrEventName = "gotthlive.event.name" AttrClientRef = "gotthlive.event.client_ref" AttrSeenServerSeq = "gotthlive.event.seen_server_seq" AttrTransitionID = "gotthlive.transition.id" AttrStateVersion = "gotthlive.state.version" AttrPatchID = "gotthlive.patch.id" AttrServerSeq = "gotthlive.server_seq" AttrFragmentID = "gotthlive.fragment.id" AttrSuppressed = "gotthlive.fragment.suppressed" AttrFrameBytes = "gotthlive.frame.bytes" AttrFrameKind = "gotthlive.frame.kind" AttrWindowDepth = "gotthlive.window.depth" AttrOriginKind = "gotthlive.origin.kind" AttrOriginSource = "gotthlive.origin.source" AttrResult = "gotthlive.result" AttrMorphMicros = "gotthlive.morph.duration_us" AttrApplyMicros = "gotthlive.apply.duration_us" AttrTimingSource = "gotthlive.timing.source" )
Span attribute keys. They are constants because a typo in an attribute key is invisible until someone queries for it and finds nothing.
const ( SpanEvent = "gotthlive.event" SpanParse = "gotthlive.parse" SpanAuthorize = "gotthlive.authorize" SpanReduce = "gotthlive.reduce" SpanRender = "gotthlive.render" SpanRenderFragment = "gotthlive.render.fragment" SpanEncode = "gotthlive.encode" SpanSend = "gotthlive.send" SpanOrigin = "gotthlive.origin" SpanClientMorph = "gotthlive.client.morph" SpanEffect = "gotthlive.effect." )
Span names, in the tree order of one event.
SpanRenderFragment was drawn in instrumentation §3.1 and declared nowhere, deliberately: "a constant nothing starts is one more thing that reads as implemented", and the section said whoever starts the span declares the constant in the same change. This is that change.
const LabelOptionCap = 256
LabelOptionCap bounds how many distinct label values this package will hold a pre-built measurement option for.
Every label domain in this library is already bounded — by the protocol's enumerations (frame kind, rejection reason, close code, patch operation, transition result, panic site), by application registration (event name), or by SourceLabelCap. The cap is the belt to those braces: a value past it is still recorded, correctly and with the same attributes, it is simply built per call rather than cached. A cache that could grow without bound would be its own memory finding.
const ProvenanceLogger = "gotthlive.provenance"
ProvenanceLogger is the name every provenance record carries, so an operator can route the stream to its own retention without matching on message text.
const SourceLabelCap = 64
SourceLabelCap bounds the origin-source label. Nothing registers an effect, so this cardinality cannot be bounded by registration the way event and fragment names are; it is bounded here instead, and the overflow counter is how an explosion becomes visible at runtime rather than never.
const SourceOverflowLabel = "other"
SourceOverflowLabel is what a source value past the cap is recorded as.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Field ¶
type Field struct {
// contains filtered or unexported fields
}
Field is one structured log attribute.
The library's log helpers take Field and nothing else, and that is the redaction boundary. There is no helper that accepts an arbitrary value, so a frame body, an application state value or an identity cannot be passed into a record by a caller who was not thinking about it. Redaction applied at the boundary is a property; redaction left to callers is a hope.
func Dur ¶
Dur returns a duration field, recorded in milliseconds because that is the unit the budgets in this project are stated in.
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger is the library's log sink. A nil *Logger is the disabled configuration; every method tests its receiver.
Leaving it disabled also disables the provenance log, which makes the reverse lookup from a captured patch back to its cause unavailable. That is a documented consequence rather than a silent one: the audit harness and the provenance soak both fail closed when the logger is absent.
func (*Logger) Debug ¶
Debug emits a per-event or per-patch record. It is off in production by default, which is a decision the consumer's handler makes.
func (*Logger) Error ¶
Error emits a record an operator should act on: a recovered panic, an abandoned effect, a protocol violation, a fatal authorization denial, a fragment identifier collision. Nothing routine reaches it.
func (*Logger) Lifecycle ¶ added in v0.2.0
Lifecycle emits a record of the live UI service's own lifecycle — its sessions drained, its goroutines joined — at Info and never sampled: there is one per stop, and the operator reading a shutdown needs every one.
func (*Logger) Provenance ¶
func (l *Logger) Provenance(ctx context.Context, p Provenance)
Provenance emits one transition record.
It is exempt from Info sampling by construction: a sampled provenance log cannot support a hundred-percent, zero-unknown guarantee, and the guarantee is the reason the stream exists.
type Metrics ¶
type Metrics struct {
// FragmentLabels enables the opt-in fragment label on the render
// histogram. It is off by default because the product of fragments and
// sessions is the one cardinality an application can grow without noticing.
FragmentLabels bool
// contains filtered or unexported fields
}
Metrics is the library's metric set. A nil *Metrics is a fully valid, fully disabled instrument: every method tests its receiver, so a configuration with no meter provider pays one branch per call site.
func NewMetrics ¶
func NewMetrics(mp metric.MeterProvider) (*Metrics, error)
NewMetrics builds the metric set from a provider. A nil provider returns a nil *Metrics, which is the disabled configuration and is not an error.
func (*Metrics) ClientTelemetryDropped ¶
ClientTelemetryDropped counts a report naming a patch this session did not send, which is either a forgery or a stale echo and is never used to fabricate a span.
func (*Metrics) ClientTiming ¶
ClientTiming records a client-reported morph and apply duration. Both are untrusted input, are named client_ so no dashboard mistakes them for a server measurement, and are bounded by the schema so a fabricated value cannot skew a histogram to infinity.
func (*Metrics) ConnectionClosed ¶
ConnectionClosed counts one close by its code label.
func (*Metrics) ConnectionOpened ¶
ConnectionOpened counts a connection admitted to the live registry.
func (*Metrics) Effect ¶
Effect counts one executed effect. The source label is capped, because nothing registers an effect.
func (*Metrics) EffectOverran ¶ added in v0.2.0
EffectOverran counts one session whose effects were still running when the drain window closed. Shutdown keeps joining them: the count is the signal that an effect is slow to honour cancellation, not that one was abandoned.
func (*Metrics) EncodeDuration ¶
EncodeDuration records validation plus marshal time.
func (*Metrics) EventReceived ¶
EventReceived counts one event dispatched to a reducer.
func (*Metrics) EventRejected ¶
EventRejected counts one event refused before a reducer saw it.
func (*Metrics) FrameReceived ¶
FrameReceived counts one accepted inbound frame and its size.
This is the connection read pump's per-frame path, and the one the G2 baseline's goroutine-stack line turned out to be about (§5.1, and the remediation section that corrects it). Every label set it uses is built once per label value and reused.
func (*Metrics) FrameRejected ¶
FrameRejected counts one refused inbound frame.
func (*Metrics) FrameSent ¶
FrameSent counts one written frame and its size. Only the framer calls it.
func (*Metrics) Goroutines ¶
Goroutines adjusts the count of goroutines this library owns.
func (*Metrics) MailboxDepth ¶
MailboxDepth samples mailbox occupancy.
func (*Metrics) OutboundInvalid ¶
OutboundInvalid counts a frame this library constructed and could not validate. Any non-zero value is actionable, and it is never a client's doing: the frame was built here, from state this library owns.
It used to say "any non-zero value is a library bug", and that sent the person holding the pager to the wrong repository. Some of what a frame carries comes from the application — Event.Contributing above all — and until D-18 an over-long one reached the outbound validator and was counted here, so an application could raise an alert about its own input that named this library as the defect. The emit path now rejects that input with an error naming the caller, so what is left really is a defect in this library: either a frame built wrong, or a bound it failed to apply before building one.
func (*Metrics) PatchCoalesced ¶
PatchCoalesced counts one patch carrying transitions that were not individually emitted.
func (*Metrics) PatchesSent ¶
PatchesSent counts emitted fragment updates by operation.
func (*Metrics) PatchesSuppressed ¶
PatchesSuppressed counts renders dropped for producing unchanged bytes.
func (*Metrics) RenderDuration ¶
RenderDuration records the time spent rendering a transition's fragments.
func (*Metrics) ResyncRequest ¶
ResyncRequest counts one resync request by result, and the snapshot's size when it produced one.
func (*Metrics) SendDuration ¶
SendDuration records time in the transport write.
func (*Metrics) SessionsActive ¶
SessionsActive follows registry membership, independently of handshake refusals.
func (*Metrics) SlowClientEvent ¶
SlowClientEvent counts one synthesized backpressure event.
func (*Metrics) SourceLabel ¶
SourceLabel maps an origin source to its label value, collapsing to "other" once the cap is reached and counting that it happened.
Traces and the provenance log carry the full value; only the label is capped, so nothing is lost, it is only moved to the signal that can afford the cardinality.
func (*Metrics) TrackedBytes ¶
TrackedBytes adjusts the exactly-sized per-session total. It covers only structures the library owns and can size: the window, the mailbox and ack backing arrays, the fragment hashes and the registry. Go has no per-goroutine heap attribution and this does not pretend otherwise.
func (*Metrics) Transition ¶
Transition counts one reducer invocation by result.
type Provenance ¶
type Provenance struct {
// SessionID scopes every other identifier here. The rest are uint64
// counters minted per session, so a value is only globally unique paired
// with this.
SessionID string
// EventID is the causal root: the server-minted identity of the event that
// caused the transition, zero when the server started it itself.
EventID uint64
// ClientRef is the browser's own handle for that event, echoed so a client
// log and a server log can be joined. It is the one untrusted value in the
// row.
ClientRef uint64
// TransitionID names the reducer invocation, one per invocation including
// one that changed nothing.
TransitionID uint64
// StateVersion rises if and only if the transition changed state. Holding
// it beside TransitionID is what makes that property checkable from the
// log alone.
StateVersion uint64
// PatchID names the emitted frame, and is zero when the transition emitted
// none — a suppressed render still gets a row.
PatchID uint64
// ServerSeq is the frame's place in the session's outbound order, zero for
// the same reason as PatchID.
ServerSeq uint64
// OriginKind is the category of cause: "client_event", "effect", "timer",
// "pubsub", "mount" or "resync".
OriginKind string
// OriginSource is the specific cause within that category, such as
// "event:cart.add". It is composed from application-supplied halves and is
// validated before it reaches a frame.
OriginSource string
// FragmentIDs are the regions this transition patched, in wire order.
// Empty means the render was suppressed.
FragmentIDs []string
// ContributingEventIDs are the events whose changes this patch carries but
// which were not individually patched, because coalescing collapsed them.
// This field is why coalescing does not lose provenance.
ContributingEventIDs []uint64
// SupersededFromSeq and SupersededThroughSeq are the inclusive server_seq
// range a resync snapshot replaced, zero on everything else. They are the
// only surviving record that the replaced patches existed.
SupersededFromSeq uint64
// SupersededThroughSeq is the upper end of that range. See
// SupersededFromSeq.
SupersededThroughSeq uint64
}
Provenance is one transition's causal row: everything needed to answer "which event produced the markup the user is looking at" from a log query alone.
A transition that emitted no patch — a suppressed render — still gets a record, with a zero patch identifier. Without it, the property that the state version rises exactly when state changed would be unverifiable, because the transitions that produced nothing would be invisible.
type Span ¶
type Span struct {
// contains filtered or unexported fields
}
Span is a nil-safe wrapper over an OpenTelemetry span.
func (Span) RecordError ¶
RecordError marks the span as failed.
func (Span) Ref ¶
Ref returns a compact reference to this span, for storing in the outbound window until the client reports back.
func (Span) SetAttributes ¶
SetAttributes adds attributes to the span.
type SpanRef ¶
type SpanRef struct {
// TraceID is the W3C trace identifier, raw rather than a trace.TraceID so
// this struct's size is a fact about this file.
TraceID [16]byte
// SpanID identifies the span within that trace.
SpanID [8]byte
// Flags is the W3C trace-flags byte, which carries the sampled bit. It is
// kept because a reconstructed context that lost it would relocate the
// sampling decision to link time.
Flags byte
// contains filtered or unexported fields
}
SpanRef is a 32-byte reference to a span, held per slot in the outbound window so a client's morph timing can be linked back to the span that encoded the patch.
It is deliberately not a trace.SpanContext. A real SpanContext is 56 to 64 bytes on amd64 once the trace flags, the remote bit and a TraceState's slice header are counted, and this library's memory budget is made of decisions this size. Reconstructing the context at link time is exact for this use, because the library never populates a TraceState.
func (SpanRef) SpanContext ¶
func (r SpanRef) SpanContext() trace.SpanContext
SpanContext reconstructs the reference for linking.
type Tracer ¶
type Tracer struct {
// contains filtered or unexported fields
}
Tracer wraps a provider. A nil *Tracer is the disabled configuration and every method on it is correct and free.
The provider is taken explicitly rather than read from the OpenTelemetry global. That is an architectural constraint and not a style preference: it is what lets this library depend on the trace and metric API submodules instead of the root module that carries the global.
func NewTracer ¶
func NewTracer(tp trace.TracerProvider) *Tracer
NewTracer builds a tracer from a provider. A nil provider returns nil, which is the disabled configuration and is not an error.
func (*Tracer) Enabled ¶
Enabled reports whether spans are being recorded.
It is the single boolean instrumentation §4.2 requires a disabled configuration to branch on, and hot-path callers check it *before* building an attribute list rather than relying on the nil check inside Start. The difference is measurable and is not a style preference: the variadic attrs escape into the tracer, so a call site that builds them unconditionally heap-allocates the backing array on every event even when tracing is off.
func (*Tracer) Start ¶
func (t *Tracer) Start(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, Span)
Start begins a span. On a nil tracer it returns the context unchanged and a span whose methods do nothing.
func (*Tracer) StartChildOf ¶
func (t *Tracer) StartChildOf(ctx context.Context, name string, parent SpanRef, attrs ...attribute.KeyValue) (context.Context, Span)
StartChildOf begins a span whose parent is a span that has already ended, named by the reference the caller carried across a goroutine boundary.
This is FR-36 clause 4's mechanism. An ended span is still a valid parent — a parent-child edge asserts that the parent's work caused the child's, not that the parent's clock encloses it — so the reference the ingress already holds is enough to make the whole server-side path descend from one root. The consequence is the one clause 4 exists for: a sampler decides once, at that root, and every span below inherits the decision. Three roots meant three decisions, and under the documented default `ParentBased(TraceIDRatioBased(0.05))` that produced 0 of 300 interactions recording both `authorize` and `event` (L9-1's C-30 measurement).
An invalid reference is not an error and is not faked: the span starts from whatever the context already carries, which for a server-initiated transition is nothing, and it becomes a root. That is truthful — no client frame authorized it — and it is why this does not silently invent an edge.
func (*Tracer) StartLinked ¶
func (t *Tracer) StartLinked(ctx context.Context, name string, to SpanRef, attrs ...attribute.KeyValue) (context.Context, Span)
StartLinked begins a span linked to a span that has already ended. It is how a client's morph timing rejoins the trace that produced the patch: the timing arrives after the encode span closed, and the span's start timestamp is derived rather than observed (instrumentation §3.3), so a parent edge would assert an enclosure this design does not measure.
It is the last link site under FR-36 clause 3. The other one — the transition linking back to authorization — became a real parent edge under clause 4, which clause 3 says is always permitted.