model

package
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package model holds the recorded data of a tracer: traces, spans and the snapshot read model built from them. It is the vocabulary the recorder and the front end share, and it depends on nothing else in the project.

Index

Constants

View Source
const (
	// AttrMemoryLimit is the memory a transaction was allowed to use, in bytes.
	// It is recorded on the trace.
	AttrMemoryLimit = "memory_limit"

	// AttrMemoryUsage is the memory in use when a span or a trace finished, in
	// bytes.
	AttrMemoryUsage = "memory_usage"
)

Well known attribute keys. The set is open; these are the ones the front end renders as sizes rather than as bare integers.

View Source
const (
	LevelInfo  = "info"
	LevelWarn  = "warn"
	LevelError = "error"
)

Log levels recorded by Trace.Info, Trace.Warn and Trace.Error. The set is closed: a log line informs, flags something worth attention, or reports a failure, and anything richer belongs in attributes.

View Source
const BackgroundHost = "internal"

BackgroundHost is the host label of traces that did not arrive over the network: cron ticks, queue consumers, startup work.

View Source
const DefaultPath = "/debug/oida"

DefaultPath is the default mount path of the debug front end.

View Source
const RequestIDHeader = "Request-Id"

RequestIDHeader carries the trace identifier on the request and the response.

View Source
const SessionCookie = "oida_session"

SessionCookie is the name of the front end session cookie.

View Source
const SessionTTL = 12 * time.Hour

SessionTTL is how long an issued session token stays valid.

Variables

View Source
var (
	// ErrNilRouter is returned when Mount is called without a router.
	ErrNilRouter = errors.New("oida: router is nil")

	// ErrNoTracer is returned when Mount is called without a tracer, which is
	// a dashboard with nothing to show.
	ErrNoTracer = errors.New("oida: options carry no tracer")

	// ErrInvalidOptions is the base error for every configuration failure.
	ErrInvalidOptions = errors.New("oida: invalid options")

	// ErrInvalidPath is returned when Options.Path is not an absolute path.
	ErrInvalidPath = fmt.Errorf("%w: path must be an absolute path", ErrInvalidOptions)

	// ErrInvalidSampleRate is returned when Options.SampleRate is outside
	// [0,100].
	ErrInvalidSampleRate = fmt.Errorf("%w: sample rate must be between 0 and 100", ErrInvalidOptions)

	// ErrTraceNotFound is returned when a trace ID is not in the ring buffer.
	ErrTraceNotFound = errors.New("oida: trace not found")

	// ErrDisabled is returned when a trace is requested from a disabled tracer.
	ErrDisabled = errors.New("oida: tracer is disabled")

	// ErrInvalidCredentials is returned when a login does not match any
	// configured user.
	ErrInvalidCredentials = errors.New("oida: invalid credentials")

	// ErrInvalidToken is returned when a session cookie or bearer token does
	// not verify against the signing secret, or has expired.
	ErrInvalidToken = errors.New("oida: invalid token")
)

The errors this project returns. Every configuration failure wraps ErrInvalidOptions, so a caller can test for the class or for the case. The root package aliases each of these, so services keep spelling them oida.Err*.

Functions

func Do

func Do(ctx context.Context, name string, fn func(context.Context) error, kind ...Kind) error

Do runs fn inside a span, records the returned error on it and ends it. The error is returned unchanged.

func IsBytes

func IsBytes(key string) bool

IsBytes reports whether key holds a size in bytes.

func NewID

func NewID(now time.Time) (string, error)

NewID returns a lexicographically sortable ULID for the given time.

func TraceHost

func TraceHost(trace *Trace) string

TraceHost returns the host a trace belongs to. Background traces have none, so they group under a stable placeholder rather than an empty string.

func TraceID

func TraceID(ctx context.Context) string

TraceID returns the identifier of the trace in ctx, or an empty string. It is the value of the Request-Id header for HTTP traces, which makes it the cheapest correlation key for logs.

func ValidID

func ValidID(id string) bool

ValidID reports whether id looks like a ULID produced by NewID. It keeps hostile input out of lookups and out of rendered links.

func WithTrace

func WithTrace(ctx context.Context, t *Trace) context.Context

WithTrace returns a context carrying the trace. Spans started from the returned context, or any context derived from it, are recorded on it.

Types

type Attributes

type Attributes map[string]any

Attributes is a set of key/value pairs recorded on a trace or on a span.

func (Attributes) Int64

func (a Attributes) Int64(key string) (int64, bool)

Int64 returns an attribute as an integer, and whether it was a number. A value decoded from JSON as a float, or carried as a decimal string, reads as the number it is.

type Auth

type Auth struct {
	// contains filtered or unexported fields
}

Auth evaluates the authentication options: the network allow list, the configured users and the token verification behind the session cookie and the Authorization header. The front end builds one per handler with NewAuth.

Every method tolerates a nil receiver, which is what an unconfigured handler holds: a nil Auth allows every network and requires no login.

func NewAuth

func NewAuth(opts Options) (*Auth, error)

NewAuth builds the authentication state out of the options. Invalid network entries and an unreadable users file are dropped, reported through Options.OnError and returned, so the caller decides whether to treat them as fatal; what loaded stays in force. Dropping an allow list entry only narrows access, so a typo fails closed.

It returns nil when no authentication option is set.

func (*Auth) Authenticate

func (a *Auth) Authenticate(ctx context.Context, username, password string) error

Authenticate checks a login against the configured users, then against the AuthorizeUser callback. Password hashes are bcrypt; a user with any other hash format never matches.

func (*Auth) LoginRequired

func (a *Auth) LoginRequired() bool

LoginRequired reports whether requests have to carry a session or a bearer token: any configured user, the authorize callback or a pre-shared secret makes credentials the way in. A bare AllowedNetworks list requires none.

func (*Auth) NetworkAllowed

func (a *Auth) NetworkAllowed(r *http.Request) bool

NetworkAllowed reports whether the request comes from an allowed network. An empty allow list allows every network; an unparsable peer address is denied.

func (*Auth) RequestUser

func (a *Auth) RequestUser(r *http.Request) (string, bool)

RequestUser returns the username a request carries, in its session cookie or in an Authorization bearer token.

func (*Auth) Session

func (a *Auth) Session(username string) (string, error)

Session issues the session token of a logged in user: an HS256 JWT with the username as its subject, valid for twelve hours.

func (*Auth) Verify

func (a *Auth) Verify(token string) (string, error)

Verify checks a token against the signing secret and returns the username it names. Only HS256 is accepted, and an expiry claim is honoured.

type HTTPInfo

type HTTPInfo struct {
	Method        string `json:"method"`
	URI           string `json:"uri"`
	Route         string `json:"route,omitempty"`
	Host          string `json:"host"`
	Protocol      string `json:"protocol"`
	RemoteAddress string `json:"remote_address"`
	UserAgent     string `json:"user_agent,omitempty"`
	Status        int    `json:"status,omitempty"`
	ResponseBytes int64  `json:"response_bytes"`
}

HTTPInfo describes the request a trace was created for.

type HostStat

type HostStat struct {
	Host string `json:"host"`

	// Requests counts every request the host was seen in, sampled or not, for
	// the lifetime of the process.
	Requests uint64 `json:"requests"`

	// Traces counts the traces of this host retained in the rolling window.
	Traces uint64 `json:"traces"`

	Errors          uint64        `json:"errors"`
	Share           float64       `json:"share_percent"`
	Routes          int           `json:"routes"`
	AverageDuration time.Duration `json:"average_duration_ns"`
	MaxDuration     time.Duration `json:"max_duration_ns"`
	Spans           uint64        `json:"spans"`
	// contains filtered or unexported fields
}

HostStat aggregates the traffic of one host.

type Kind

type Kind string

Kind classifies the work a span measured. The set is open: an unrecognized value is valid, renders with the fallback color and groups on its own in the timeline.

const (
	KindInternal Kind = "internal"
	KindHTTP     Kind = "http"
	KindDatabase Kind = "database"
	KindExternal Kind = "external"
	KindTemplate Kind = "template"
	KindCache    Kind = "cache"
	KindQueue    Kind = "queue"
)

The kinds the front end colours and groups by. A value outside this set is still valid.

func (Kind) Color

func (k Kind) Color() string

Color returns the stable UI color of the kind. This is a categorical scale, not a theme: the hues are spread and saturated so two segments of a timeline can be told apart at a glance, which matters more here than restraint. Text on top of these is the dark ink, so every one of them carries a label.

func (Kind) String

func (k Kind) String() string

String implements fmt.Stringer.

type LogEntry

type LogEntry struct {
	Time    time.Time `json:"time"`
	Level   string    `json:"level"`
	Message string    `json:"message"`
	SpanID  int       `json:"span_id,omitempty"`

	// RequestID is the ID of the trace the entry was recorded on, the value
	// the Request-Id header carries. Every entry of a trace shares it, and it
	// keeps an entry attributable once the log is read apart from its trace.
	RequestID string `json:"request_id,omitempty"`

	// Attributes carry the slog-style key/value arguments of the call. The
	// message is stored verbatim, never formatted.
	Attributes Attributes `json:"attributes,omitempty"`
}

LogEntry is one log line recorded on a trace. Entries live in one slice on the trace, in write order; SpanID links each entry to the span that was active when it was written, and is zero for an entry written outside any open span.

type Memory

type Memory struct {
	HeapAlloc     uint64  `json:"heap_alloc_bytes"`
	HeapInuse     uint64  `json:"heap_inuse_bytes"`
	HeapObjects   uint64  `json:"heap_objects"`
	StackInuse    uint64  `json:"stack_inuse_bytes"`
	System        uint64  `json:"system_bytes"`
	NextGC        uint64  `json:"next_gc_bytes"`
	NumGC         uint32  `json:"gc_cycles"`
	GCPauseTotal  uint64  `json:"gc_pause_total_ns"`
	GCCPUFraction float64 `json:"gc_cpu_fraction"`
	Limit         uint64  `json:"memory_limit_bytes,omitempty"`
}

Memory describes current process memory and GC pressure.

func ReadMemory

func ReadMemory() Memory

ReadMemory reads the process memory and GC pressure the dashboard shows, through runtime/metrics: unlike runtime.ReadMemStats, the read does not stop the world, so an open live view does not pause the process it watches. The pause total and the GC CPU fraction are computed from counters and are indicative, the way MemoryUse documents. Limit is the caller's to fill.

type MemoryUse

type MemoryUse struct {
	HeapDelta      int64         `json:"heap_delta_bytes"`
	AllocatedBytes uint64        `json:"allocated_bytes"`
	Allocations    uint64        `json:"allocations"`
	GCCycles       uint32        `json:"gc_cycles"`
	GCPause        time.Duration `json:"gc_pause_ns"`
}

MemoryUse holds the process-wide allocation deltas observed while a trace ran. Concurrent traces overlap, so the values are indicative, not exact.

type Options

type Options struct {
	// Path is the mount path of the debug front end.
	Path string `yaml:"path"`

	// ServiceName is displayed in the front end and recorded on every trace.
	ServiceName string `yaml:"service_name"`

	// Enabled records traces. A disabled tracer passes requests through.
	Enabled bool `yaml:"enabled"`

	// RingBufferSize is the number of completed traces retained.
	RingBufferSize int `yaml:"ring_buffer_size"`

	// TopRequests is the maximum number of groups in rolling statistics.
	TopRequests int `yaml:"top_requests"`

	// MaxSpansPerTrace bounds the spans recorded in a single trace. Excess
	// spans are counted in Trace.DroppedSpans. Zero means unlimited.
	MaxSpansPerTrace int `yaml:"max_spans_per_trace"`

	// SampleRate is the percentage of requests traced, between 0 and 100. It
	// is ignored when Sampler is set.
	SampleRate float64 `yaml:"sample_rate"`

	// TrackMemoryUse records process-wide allocation changes for each trace.
	TrackMemoryUse bool `yaml:"track_memory_use"`

	// TrustRequestID reuses a client supplied Request-Id header. Only enable
	// this behind a trusted proxy.
	TrustRequestID bool `yaml:"trust_request_id"`

	// IgnorePaths lists request paths that are never traced. Entries ending in
	// "/*" match by prefix.
	IgnorePaths []string `yaml:"ignore_paths"`

	// RefreshInterval is the fallback auto refresh interval of the live view in
	// seconds, used when the browser cannot stream. Zero disables it.
	RefreshInterval int `yaml:"refresh_interval"`

	// LiveStream serves the live view over server sent events, so recorded
	// traces appear as they happen instead of on a timer.
	LiveStream bool `yaml:"live_stream"`

	// CaptureLogs records Trace.Info and Trace.Error log entries on traces.
	// Disabled, Info does nothing and Error records its formatted text as an
	// error on the active span, the way RecordError does, so the message is
	// not lost.
	CaptureLogs bool `yaml:"capture_logs"`

	// ReadEnv applies the OIDA_* environment to these options when the tracer
	// is built. NewOptions turns it on, so a service configured in code still
	// takes its deployment settings from the environment; options built as a
	// literal leave it off.
	ReadEnv bool `yaml:"read_env"`

	// Sampler decides whether a request is traced. It replaces SampleRate.
	Sampler Sampler `yaml:"-"`

	// Storage retains completed traces. Defaults to a memory ring buffer sized
	// by RingBufferSize; disk storage retains them across restarts.
	Storage Storage `yaml:"-"`

	// RouteFunc returns the routed pattern of a request, so statistics group
	// /users/1 and /users/2 into GET /users/{id}. With chi:
	//
	//	opts.RouteFunc = func(r *http.Request) string {
	//		return chi.RouteContext(r.Context()).RoutePattern()
	//	}
	//
	// The function decides on its own: returning an empty string means the
	// request has no route worth grouping by, and it groups by path instead.
	// A nil function falls back to the pattern the router recorded on the
	// request, which is what http.ServeMux and chi both set.
	RouteFunc func(r *http.Request) string `yaml:"-"`

	// OnError receives storage and recording errors. The package never writes
	// to stdout or stderr, so this is the only way to observe them.
	OnError func(error) `yaml:"-"`

	// Authorize gates access to the debug front end. A nil function allows
	// every request.
	Authorize func(r *http.Request) bool `yaml:"-"`

	// Clock is the time source of the tracer. Defaults to time.Now.
	Clock func() time.Time `yaml:"-"`

	// AllowedNetworks restricts the debug front end to peers inside these
	// CIDR ranges, such as 127.0.0.0/8 or 10.0.0.0/8. IPv4 and IPv6 both
	// work; an empty list allows every network. A request from outside
	// receives a 404 response, like a failed Authorize.
	AllowedNetworks []string `yaml:"allowed_networks"`

	// Users maps usernames to bcrypt password hashes for the front end login
	// screen. Setting any user puts the front end behind a login.
	Users map[string]string `yaml:"users"`

	// UsersFile is the path of an .htpasswd style file with one
	// username:bcrypt-hash per line. It is read once, when the front end is
	// mounted, and merged under Users.
	UsersFile string `yaml:"users_file"`

	// SigningSecret signs the session cookie issued by the login screen and
	// verifies "Authorization: Bearer" JWTs (HS256). When empty a per-process
	// secret is generated: logins work, but sessions do not survive a restart
	// and no externally minted token verifies.
	SigningSecret string `yaml:"signing_secret"`

	// AuthorizeUser authenticates a login when the configured users do not.
	// Returning nil grants a session naming the given username.
	AuthorizeUser func(ctx context.Context, username, password string) error `yaml:"-"`
	// contains filtered or unexported fields
}

Options configures telemetry behaviour, the debug front end and the middleware. Take NewOptions and override what you need, so fields added in later versions keep their defaults.

func NewOptions

func NewOptions(serviceName string) Options

NewOptions returns the default options for the named service.

func (Options) Authorized

func (o Options) Authorized(r *http.Request) bool

Authorized reports whether r may access the debug front end. The front end asks before it serves anything, including its assets.

func (Options) Validate

func (o Options) Validate() error

Validate reports whether the options are usable. Every failure wraps ErrInvalidOptions.

func (Options) WithDefaults

func (o Options) WithDefaults() Options

WithDefaults returns a usable copy of the options. Options created by NewOptions preserve explicit zero values; an uninitialized Options receives the numeric defaults for backward compatibility.

type PoolEstimate

type PoolEstimate struct {
	Samples               uint64 `json:"samples"`
	AverageAllocatedBytes uint64 `json:"average_allocated_bytes"`
	BeforeNextGC          uint64 `json:"traces_before_next_gc,omitempty"`
	WithinMemoryLimit     uint64 `json:"traces_within_memory_limit,omitempty"`
}

PoolEstimate is a heuristic concurrency estimate derived from observed per-trace allocations.

type Recorder

type Recorder interface {
	// StartTrace begins a trace the caller must complete with Finish.
	StartTrace(ctx context.Context, name string) (context.Context, *Trace, error)

	// Finish completes a trace and retains it.
	Finish(t *Trace)

	// Snapshot returns a race free copy of the recorded state.
	Snapshot() Snapshot

	// Traces returns the retained traces, newest first.
	Traces() []Trace

	// Trace returns the retained or in flight trace with the given ID.
	Trace(id string) (Trace, bool)

	// Live returns the traces currently in flight, newest first.
	Live() []Trace

	// Subscribe returns a channel notified whenever a trace starts or
	// completes, and a function releasing it.
	Subscribe() (<-chan struct{}, func())

	// Options returns the options the recorder was built with.
	Options() Options

	// Enabled reports whether the recorder records traces.
	Enabled() bool

	// SetEnabled turns recording on or off at runtime.
	SetEnabled(enabled bool)

	// Reset drops every retained trace and the lifetime counters.
	Reset()

	// ReportError forwards a failure to Options.OnError.
	ReportError(err error)
}

Recorder is the substitutable surface of a tracer: the write side the instrumentation records through, and the read side the debug front end renders from. The root package's *Tracer implements it; code that only needs to record and read back traces can depend on this interface instead of the concrete tracer.

type Sampler

type Sampler interface {
	Sample(r *http.Request) bool
}

Sampler decides whether a request is traced. The decision is taken before a trace is allocated, so rejecting a request costs one interface call.

type SamplerFunc

type SamplerFunc func(r *http.Request) bool

SamplerFunc adapts a function to the Sampler interface.

func (SamplerFunc) Sample

func (f SamplerFunc) Sample(r *http.Request) bool

Sample implements Sampler.

type Snapshot

type Snapshot struct {
	Service    string        `json:"service"`
	StartedAt  time.Time     `json:"started_at"`
	Uptime     time.Duration `json:"uptime_ns"`
	PID        int           `json:"pid"`
	GoVersion  string        `json:"go_version"`
	GOMAXPROCS int           `json:"gomaxprocs"`
	Goroutines int           `json:"goroutines"`

	Total   uint64 `json:"total_requests"`
	Sampled uint64 `json:"sampled_traces"`
	Dropped uint64 `json:"dropped_traces"`
	Active  int    `json:"active_traces"`

	// Errors counts recorded traces that failed, and SLA is the share of
	// recorded traces that did not, as a percentage. Requests the sampler
	// rejected have unknown outcomes, so they are not in the denominator.
	Errors uint64  `json:"failed_traces"`
	SLA    float64 `json:"sla_percent"`

	StateTime []StateDuration `json:"state_time"`
	Memory    Memory          `json:"memory"`
	Pool      PoolEstimate    `json:"pool_estimate"`

	Live       []Trace `json:"live"`
	Log        []Trace `json:"log"`
	Statistics Stats   `json:"statistics"`
}

Snapshot is the complete read model of a tracer at one point in time.

type Span

type Span struct {
	ID         int           `json:"id"`
	ParentID   int           `json:"parent_id,omitempty"`
	TraceID    string        `json:"trace_id"`
	Name       string        `json:"name"`
	Kind       Kind          `json:"kind"`
	StartedAt  time.Time     `json:"started_at"`
	Duration   time.Duration `json:"duration_ns,omitempty"`
	Depth      int           `json:"depth"`
	Filename   string        `json:"filename,omitempty"`
	Line       int           `json:"line,omitempty"`
	Attributes Attributes    `json:"attributes,omitempty"`

	// ErrorText is the message of the error recorded on the span. The JSON
	// key stays "error"; the Go name makes room for the Error log method.
	ErrorText string `json:"error,omitempty"`
	// contains filtered or unexported fields
}

Span is one timed operation within a trace. Every method is safe to call on a nil span, which is what Start returns when the context carries no trace or the trace was not sampled.

func SpanFromContext

func SpanFromContext(ctx context.Context) *Span

SpanFromContext returns the innermost span in ctx, or nil.

func Start

func Start(ctx context.Context, name string, kind ...Kind) (context.Context, *Span)

Start records a span in the trace carried by ctx and returns a context carrying it. When ctx has no trace, or the trace was not sampled, it returns ctx unchanged and a nil span: every span method tolerates that.

ctx, span := oida.Start(ctx, "SELECT users", oida.KindDatabase)
defer span.End()

The kind is optional and defaults to KindInternal.

func StartSpan

func StartSpan(ctx context.Context, name string, kind ...Kind) *Span

StartSpan records a span without deriving a context. Use it for leaf spans that will not nest.

func (*Span) Context

func (s *Span) Context(ctx context.Context) context.Context

Context returns a context with the span as the active parent, so spans started from it nest below this one. The context comes preallocated with the span, so one derivation per span costs nothing; a second call rebinds that same context to the new parent.

func (*Span) Elapsed

func (s *Span) Elapsed() time.Duration

Elapsed returns the recorded duration, or the time since the span started when it has not ended yet.

func (*Span) End

func (s *Span) End()

End records the span duration. It is idempotent: a deferred End plus an explicit End on an error path record one duration, not two.

func (*Span) EndWithError

func (s *Span) EndWithError(err error)

EndWithError records err on the span and ends it.

func (*Span) Ended

func (s *Span) Ended() bool

Ended reports whether the span was ended.

func (*Span) Err

func (s *Span) Err() error

Err returns the error recorded on the span, or nil. A span decoded from JSON kept the message and not the value, and reports an error carrying it.

func (*Span) Error

func (s *Span) Error(message string, args ...any)

Error records an error-level log entry on the trace of the span, attributed to this span; marking the transaction failed is Span.RecordError. With log capture disabled it records the text through RecordError, and it tolerates a nil span.

func (*Span) Inert

func (s *Span) Inert() Span

Inert returns a copy of the span detached from the trace that recorded it, safe to embed in a render model or hand to a consumer. Copying a span value on its own would carry the lock and the back reference with it, and would read the fields of a span another goroutine is still recording into.

func (*Span) Info

func (s *Span) Info(message string, args ...any)

Info records an informational log entry on the trace of the span, attributed to this span, from slog-style key/value pairs. It does nothing with log capture disabled, and tolerates a nil span.

func (*Span) RecordError

func (s *Span) RecordError(err error)

RecordError records an error on the span and marks the trace as failed. A nil error is ignored.

func (*Span) SetAttribute

func (s *Span) SetAttribute(key string, value any)

SetAttribute records a key/value pair on the span.

func (*Span) SetAttributes

func (s *Span) SetAttributes(attributes Attributes)

SetAttributes records several key/value pairs on the span.

func (*Span) SetName

func (s *Span) SetName(name string)

SetName replaces the span name.

func (*Span) SetSource

func (s *Span) SetSource(filename string, line int)

SetSource records the source location shown in the span table.

func (*Span) SourceText

func (s *Span) SourceText() string

SourceText returns the "file:L12" location of the span, or an empty string.

func (*Span) Trace

func (s *Span) Trace() *Trace

Trace returns the trace the span belongs to.

func (*Span) Warn

func (s *Span) Warn(message string, args ...any)

Warn records a warn-level log entry on the trace of the span, attributed to this span, from slog-style key/value pairs. It does nothing with log capture disabled, and tolerates a nil span.

type Spans

type Spans []*Span

Spans is the recorded span list of a trace.

func (Spans) Find

func (s Spans) Find(id int) *Span

Find returns the span with the id, or nil when the trace recorded none with it, which includes an entry written outside any span.

type State

type State string

State is the scoreboard state of an in-flight trace. The one-character values follow the convention used by servers such as lighttpd.

const (
	StateWaiting    State = "_"
	StateStarting   State = "s"
	StateReading    State = "R"
	StateProcessing State = "P"
	StateWriting    State = "W"
	StateKeepalive  State = "K"
	StateClosing    State = "C"
	StateError      State = "E"
)

The states a trace moves through, from waiting for work to the error it ended on.

func States

func States() []State

States returns every known state in display order. The returned slice is shared; callers read it.

func (State) Label

func (s State) Label() string

Label returns the human readable name of the state.

type StateDuration

type StateDuration struct {
	State    State         `json:"state"`
	Label    string        `json:"label"`
	Duration time.Duration `json:"duration_ns"`
	Share    float64       `json:"share_percent"`
}

StateDuration is the lifetime trace time observed in one scoreboard state.

func StateDurations

func StateDurations(durations map[State]time.Duration) []StateDuration

StateDurations converts accumulated per state time into display order with shares.

type Statistic

type Statistic struct {
	Name                  string        `json:"name"`
	Host                  string        `json:"host,omitempty"`
	Count                 uint64        `json:"count"`
	Errors                uint64        `json:"errors"`
	Share                 float64       `json:"share_percent"`
	AverageDuration       time.Duration `json:"average_duration_ns"`
	MaxDuration           time.Duration `json:"max_duration_ns"`
	AverageResponseBytes  uint64        `json:"average_response_bytes"`
	AverageAllocatedBytes uint64        `json:"average_allocated_bytes"`
	AverageSpans          float64       `json:"average_spans"`
	// contains filtered or unexported fields
}

Statistic aggregates one group of traces in the rolling window.

type Stats

type Stats struct {
	WindowSize  int         `json:"window_size"`
	WindowLimit int         `json:"window_limit"`
	TopLimit    int         `json:"top_limit"`
	Top         []Statistic `json:"top"`
	Hosts       []HostStat  `json:"hosts"`
}

Stats contains the most frequent trace groups in the rolling window.

func Statistics

func Statistics(window []Trace, windowLimit, topLimit int, requests map[string]uint64) Stats

Statistics aggregates the rolling window of completed traces. Traces group by host and by routed pattern where one is known, so /users/1 and /users/2 aggregate into GET /users/{id}. Lifetime request counts per host come from the tracer, because requests the sampler rejected never became traces.

type Storage

type Storage interface {
	// Save retains a completed trace. The pointer is only lent for the
	// call: a driver copies what it keeps, with Clone or CloneInto, and
	// must not hold on to it.
	Save(ctx context.Context, trace *Trace) error

	// Load returns a retained trace, or ErrTraceNotFound.
	Load(ctx context.Context, id string) (Trace, error)

	// List returns retained traces newest first, at most limit of them. A limit
	// of zero or less returns everything retained.
	List(ctx context.Context, limit int) ([]Trace, error)

	// Len returns the number of retained traces.
	Len(ctx context.Context) (int, error)

	// Cap returns the retention limit, or zero when unbounded.
	Cap() int

	// Reset drops every retained trace.
	Reset(ctx context.Context) error

	// Prune drops retained traces older than maxAge. A driver with nothing
	// to prune returns nil.
	Prune(ctx context.Context, maxAge time.Duration) error

	// Restore fills the read path from what the driver persisted, so a new
	// process can list what an earlier one recorded. A driver holding nothing
	// of its own returns nil.
	Restore(ctx context.Context) error
}

Storage retains completed traces. Implementations must be safe for concurrent use: the tracer writes from request goroutines and reads from the debug front end at the same time.

Two implementations ship in the storage package: a bounded ring buffer, and a bounded folder of JSON documents.

type Trace

type Trace struct {
	ID        string        `json:"id"`
	Name      string        `json:"name"`
	Service   string        `json:"service,omitempty"`
	State     State         `json:"state"`
	StartedAt time.Time     `json:"started_at"`
	UpdatedAt time.Time     `json:"updated_at"`
	Duration  time.Duration `json:"duration_ns"`

	// ErrorText is the message of the error recorded on the trace. The JSON
	// key stays "error"; the Go name makes room for the Error log method.
	ErrorText string `json:"error,omitempty"`

	// InFlight reports whether the trace was still running when it was copied.
	InFlight bool `json:"in_flight,omitempty"`

	HTTP   *HTTPInfo `json:"http,omitempty"`
	Memory MemoryUse `json:"memory"`

	// Attributes is what the transaction recorded about itself, such as the
	// memory limit it ran under.
	Attributes Attributes `json:"attributes,omitempty"`

	Spans        Spans `json:"spans,omitempty"`
	DroppedSpans int   `json:"dropped_spans,omitempty"`

	// Logs are the log lines recorded while the trace ran, in write order,
	// each linked to the span that was active when it was written. They are
	// bounded by the span limit; excess entries are counted in DroppedLogs.
	Logs        []LogEntry `json:"logs,omitempty"`
	DroppedLogs int        `json:"dropped_logs,omitempty"`
	// contains filtered or unexported fields
}

Trace is one recorded unit of work: an HTTP request, a background job, a cron tick or a startup step. Every method is safe to call on a nil trace.

func NewTrace

func NewTrace(id, name string, opts TraceOptions) *Trace

NewTrace returns a trace ready to record spans. The recorder passes the parts of its configuration a trace needs; everything else about a trace is set by recording it.

Every trace is backed by a pooled box: the trace, its mutex, its HTTP info and slots for the first spans share one reused allocation. A trace that is never released is collected like any other value; Release is the recorder's, for the traces whose lifetime it owns end to end.

func TraceFromContext

func TraceFromContext(ctx context.Context) *Trace

TraceFromContext returns the trace in ctx, or nil.

func (*Trace) Attribute

func (t *Trace) Attribute(key string) (any, bool)

Attribute returns an attribute of the trace, and whether it was recorded.

func (*Trace) Clone

func (t *Trace) Clone() Trace

Clone returns an inert deep copy of the trace, safe to hand to snapshot consumers. Mutating the copy cannot affect the tracer.

func (*Trace) CloneInto

func (t *Trace) CloneInto(dst *Trace)

CloneInto writes an inert deep copy of the trace into dst, reusing the allocations dst already owns when they fit: the HTTPInfo, the span block, the span pointer slice and the log slice are rewritten in place. It is how the ring buffer retains a trace without allocating for it, and Clone with an empty destination.

func (*Trace) Current

func (t *Trace) Current() *Span

Current returns the innermost open span: the most recently started span that has not ended. It is nil on a nil trace and when no span is open, so a caller holding only the trace can still attribute work to the active span.

func (*Trace) Durations

func (t *Trace) Durations() map[State]time.Duration

Durations returns the time spent per state, including the time accumulated in the current state up to now.

func (*Trace) Elapsed

func (t *Trace) Elapsed() time.Duration

Elapsed returns the recorded duration, or the time since the trace started when it is still in flight.

func (*Trace) Err

func (t *Trace) Err() error

Err returns the error recorded on the trace, or nil. A trace decoded from JSON kept the message and not the value, and reports an error carrying it.

func (*Trace) Error

func (t *Trace) Error(message string, args ...any)

Error records an error-level log entry on the trace, attributed to the innermost open span when one is open. It only logs: the trace state and Trace.ErrorText are untouched, which is RecordError's job. When log capture is disabled it records the formatted text through RecordError instead, on the innermost open span when one is open, so the message is not lost. Safe to call on a nil trace.

func (*Trace) Failed

func (t *Trace) Failed() bool

Failed reports whether the trace recorded an error, by message or by state.

func (*Trace) Finish

func (t *Trace) Finish()

Finish closes the trace, ending every open span. It is idempotent.

func (*Trace) HasKind

func (t *Trace) HasKind(kind Kind) bool

HasKind reports whether the trace recorded a span of the given kind.

func (*Trace) Info

func (t *Trace) Info(message string, args ...any)

Info records an informational log entry on the trace, attributed to the innermost open span when one is open. No context is needed: a caller holding only the trace still lands the entry on the right span.

trace.Info("cache warmed", "keys", 128)

Args are slog-style key/value pairs, kept on LogEntry.Attributes; the message is stored verbatim. When log capture is disabled it does nothing. Safe to call on a nil trace.

func (*Trace) Kinds

func (t *Trace) Kinds() []Kind

Kinds returns the distinct span kinds recorded in the trace, in first use order.

func (*Trace) RecordError

func (t *Trace) RecordError(err error)

RecordError records an error on the trace and moves it to StateError. It is what Span.RecordError calls after recording on the span. A nil error is ignored.

func (*Trace) RecordMemory

func (t *Trace) RecordMemory()

RecordMemory records the process-wide allocation deltas observed while the trace ran. Concurrent traces overlap, so the values are indicative: this is the process moving, measured across one trace, not the trace on its own.

func (*Trace) Release

func (t *Trace) Release()

Release returns the trace's box to the pool. The recorded values stay in place until the box is reused, so a holder may still read the trace; once NewTrace picks the box up again, the memory belongs to the new trace. The recorder releases only the traces whose lifetime it owns end to end. It is safe on a nil trace and a no-op on a clone or a decoded trace, which have no box.

func (*Trace) ReleaseOnCollect

func (t *Trace) ReleaseOnCollect()

ReleaseOnCollect arranges for the box to return to the pool once the collector finds the trace unreachable: Release for a trace whose lifetime the recorder does not own, such as one handed out by StartTrace. Any holder keeps the box reachable, so the memory moves only after the last reference is gone. It is safe on a nil trace and a no-op without a box.

func (*Trace) Root

func (t *Trace) Root() *Span

Root returns the first recorded span, or nil.

func (*Trace) SetAttribute

func (t *Trace) SetAttribute(key string, value any)

SetAttribute records a key/value pair on the trace. Use it for what holds for the whole transaction; what holds for one operation belongs on its span.

func (*Trace) SetAttributes

func (t *Trace) SetAttributes(attributes Attributes)

SetAttributes records several key/value pairs on the trace.

func (*Trace) SetHTTPInfo

func (t *Trace) SetHTTPInfo(info *HTTPInfo)

SetHTTPInfo records the request metadata of an HTTP trace, copying the value. The pointer is not retained, so a caller's stack-allocated HTTPInfo stays on the stack; a pooled trace copies into its own box. A nil info leaves the trace as it is.

func (*Trace) SetName

func (t *Trace) SetName(name string)

SetName replaces the trace name.

func (*Trace) SetResponse

func (t *Trace) SetResponse(status int, bytes int64, route string)

SetResponse records the response metadata of an HTTP trace.

func (*Trace) SetState

func (t *Trace) SetState(state State)

SetState transitions the trace state, accumulating the time spent in the previous state.

func (*Trace) SpanCount

func (t *Trace) SpanCount() int

SpanCount returns the number of recorded spans.

func (*Trace) StartSpan

func (t *Trace) StartSpan(ctx context.Context, name string, kind ...Kind) (context.Context, *Span)

StartSpan records a span whose parent is the active span in ctx. The returned context carries the new span, so nested StartSpan calls nest below it.

func (*Trace) StateTimes

func (t *Trace) StateTimes() (out [numStates]time.Duration)

StateTimes returns the time spent per state, indexed as States lists them. It is Durations without the map, for a caller aggregating many traces.

func (*Trace) Status

func (t *Trace) Status() int

Status returns the HTTP response status of the trace, or zero.

func (*Trace) TrackMemory

func (t *Trace) TrackMemory()

TrackMemory records the process memory counters the trace started with, so RecordMemory can report what it allocated.

func (*Trace) Warn

func (t *Trace) Warn(message string, args ...any)

Warn records a warn-level log entry on the trace, attributed to the innermost open span when one is open. Args are slog-style key/value pairs, kept on LogEntry.Attributes. When log capture is disabled it does nothing. Safe to call on a nil trace.

type TraceOptions

type TraceOptions struct {
	// Service is recorded on every trace, so a snapshot names the process it
	// came from.
	Service string

	// MaxSpans bounds the spans one trace records. Excess spans are counted in
	// Trace.DroppedSpans. It also bounds the log entries of the trace, whose
	// excess is counted in Trace.DroppedLogs. Zero means unlimited.
	MaxSpans int

	// Clock is the time source of the trace. A nil clock uses time.Now.
	Clock func() time.Time

	// CaptureLogs records Info and Error log entries on the trace. Disabled,
	// Info is a no-op and Error records through RecordError instead.
	CaptureLogs bool
}

TraceOptions is the configuration a trace is recorded with. The recorder derives it from its own options, so this package stays free of them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL