observability

package
v2.111.1 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package observability gives CortexDB the three things an operator needs from a process that calls itself a service: counters, gauges and histograms that can be scraped, the same numbers on /debug/vars for a human with curl, and a tracing seam that some other module can fill in.

It deliberately depends on nothing outside the standard library. The Prometheus text exposition format is a few dozen lines to emit correctly, and this repo would rather carry those lines than carry a client library and its transitive tree — the same trade already made for LLM clients, which live behind interfaces (cortexdb.Embedder, graphflow.JSONGenerator) so their SDKs stay out of pkg/.

Nothing here starts a goroutine or a timer. A Registry that is never scraped costs one map and a few atomics; a Registry that is never constructed costs nothing at all.

Index

Constants

This section is empty.

Variables

View Source
var DefaultLatencyBuckets = []float64{
	0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10,
}

DefaultLatencyBuckets covers the range an RPC against a local SQLite brain actually spends: sub-millisecond for a cached lookup, up to ten seconds for a pathological SPARQL query. Buckets are the one histogram decision that cannot be changed later without discarding recorded history, so they are declared once here rather than guessed at each call site.

Functions

This section is empty.

Types

type Counter

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

Counter is a single counter series.

func (*Counter) Add

func (c *Counter) Add(delta float64)

Add adds delta, which must not be negative.

func (*Counter) Inc

func (c *Counter) Inc()

Inc adds one.

func (*Counter) Value

func (c *Counter) Value() float64

Value returns the current count.

type CounterVec

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

CounterVec is a counter family. Counters only go up; a process restart resetting one to zero is expected, and is what rate() is built to absorb.

func (*CounterVec) Inc

func (c *CounterVec) Inc(labelValues ...string)

Inc adds one to the counter for these label values.

func (*CounterVec) With

func (c *CounterVec) With(labelValues ...string) *Counter

With returns the counter for one label combination, creating it on first use.

type Gauge

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

Gauge is a single gauge series.

func (*Gauge) Add

func (g *Gauge) Add(delta float64)

Add moves the value by delta, which may be negative.

func (*Gauge) Dec

func (g *Gauge) Dec()

Dec subtracts one.

func (*Gauge) Inc

func (g *Gauge) Inc()

Inc adds one.

func (*Gauge) Set

func (g *Gauge) Set(v float64)

Set replaces the value.

func (*Gauge) Value

func (g *Gauge) Value() float64

Value returns the current value.

type GaugeVec

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

GaugeVec is a gauge family: a value that may move both ways, such as the number of RPCs in flight.

func (*GaugeVec) With

func (g *GaugeVec) With(labelValues ...string) *Gauge

With returns the gauge for one label combination, creating it on first use.

type Histogram

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

Histogram is a single histogram series.

func (*Histogram) Count

func (h *Histogram) Count() uint64

Count returns the number of observations.

func (*Histogram) Observe

func (h *Histogram) Observe(v float64)

Observe records one value.

func (*Histogram) Sum

func (h *Histogram) Sum() float64

Sum returns the sum of all observed values.

type HistogramVec

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

HistogramVec is a histogram family.

func (*HistogramVec) Observe

func (h *HistogramVec) Observe(v float64, labelValues ...string)

Observe records one value against these label values.

func (*HistogramVec) With

func (h *HistogramVec) With(labelValues ...string) *Histogram

With returns the histogram for one label combination, creating it on first use.

type Registry

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

Registry holds every metric family a process exports.

It is safe for concurrent use. Finding the series for a set of label values takes a read lock; moving the value itself is a plain atomic, so goroutines recording against different series never serialize on each other. Only the first sighting of a label combination takes the write lock.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty registry. It starts nothing.

func (*Registry) ExpvarVar

func (r *Registry) ExpvarVar() expvar.Var

ExpvarVar returns the registry as an expvar.Var. It is an expvar.Func, so the numbers are read at the moment /debug/vars is fetched rather than copied at publication time — nothing polls, and an unread registry costs nothing.

func (*Registry) Handler

func (r *Registry) Handler() http.Handler

Handler serves the registry in Prometheus text format. The caller mounts it wherever it likes — this package never touches http.DefaultServeMux and never opens a listener, because a library that binds a port decides an operator's firewall rules for them.

func (*Registry) NewCounter

func (r *Registry) NewCounter(name, help string, labelNames ...string) *CounterVec

NewCounter declares a counter family. Calling it again with the same name, kind and label names returns the same family.

func (*Registry) NewGauge

func (r *Registry) NewGauge(name, help string, labelNames ...string) *GaugeVec

NewGauge declares a gauge family.

func (*Registry) NewHistogram

func (r *Registry) NewHistogram(name, help string, buckets []float64, labelNames ...string) *HistogramVec

NewHistogram declares a histogram family. Passing nil buckets uses DefaultLatencyBuckets. Bounds are sorted and de-duplicated; the +Inf bound is implicit and must not be passed.

func (*Registry) PublishExpvar

func (r *Registry) PublishExpvar(name string) error

PublishExpvar publishes the registry under name so it appears on /debug/vars.

expvar.Publish panics on a duplicate name, which would turn a second Open of the same process — the normal shape of a test — into a crash, so the name is checked first and a duplicate is returned as an error the caller can ignore or log.

Note that importing expvar at all registers /debug/vars on http.DefaultServeMux, a side effect of the standard library that this package inherits and cannot opt out of. A server that does not want the default mux exposed should serve expvar.Handler() on a mux of its own and not use DefaultServeMux, which is what cmd/ does.

func (*Registry) Snapshot

func (r *Registry) Snapshot() map[string]any

Snapshot renders the registry as plain Go values, which is what the expvar view is built from and what a test can assert against without parsing text.

A family with no labels maps straight to its value; a family with labels maps to one entry per series, keyed by the same {name="value"} rendering the Prometheus output uses, so the two views are recognisably the same numbers.

func (*Registry) WriteText

func (r *Registry) WriteText(w io.Writer) error

WriteText renders the whole registry in Prometheus text format. It is not called WriteTo because that name belongs to io.WriterTo, whose signature returns a byte count nobody here would use.

The rendering is a point-in-time read of atomics rather than a locked-out snapshot: two different families can be a few microseconds apart in the same scrape. That is the same guarantee every Prometheus client gives, and it is the right one — freezing the registry for the length of a scrape would make every RPC wait on a monitoring system.

type Span

type Span interface {
	// SetAttribute records a bounded, low-cardinality attribute.
	SetAttribute(key, value string)
	// End finishes the span. A non-nil err marks it failed.
	End(err error)
}

Span is one unit of work. Every Start must be matched by exactly one End.

Attribute values are strings rather than any, because the only attributes worth attaching here are the same bounded ones that make acceptable metric labels — a method name, a status code — and a typed variant would exist mostly to invite unbounded ones.

type Tracer

type Tracer interface {
	Start(ctx context.Context, name string) (context.Context, Span)
}

Tracer is the seam a tracing backend plugs into.

It is deliberately an interface this repo owns rather than an import of go.opentelemetry.io/otel, for the same reason cortexdb.Embedder is an interface rather than an import of an LLM SDK: the dependency belongs to whoever wants the feature, not to everyone who links the library. OTel's module graph is large and versions on its own schedule; a CortexDB embedded in someone else's binary should not drag it in to record a counter.

An adapter is a dozen lines and belongs outside pkg/ — in the caller's binary, or in examples/, where the openai-go client already lives:

type otelTracer struct{ t trace.Tracer }

func (o otelTracer) Start(ctx context.Context, name string) (context.Context, observability.Span) {
	ctx, span := o.t.Start(ctx, name)
	return ctx, otelSpan{span}
}

type otelSpan struct{ s trace.Span }

func (o otelSpan) SetAttribute(k, v string) { o.s.SetAttributes(attribute.String(k, v)) }
func (o otelSpan) End(err error) {
	if err != nil {
		o.s.RecordError(err)
		o.s.SetStatus(codes.Error, err.Error())
	}
	o.s.End()
}

The returned context must be the one passed down to the handler, so a backend that propagates span context through context.Context keeps its parent-child links. A tracer that needs nothing from the context returns it unchanged.

var NopTracer Tracer = nopTracer{}

NopTracer is the default when no tracer is configured. Its Start returns the context unchanged and a zero-size span, so an unconfigured tracer allocates nothing and the call is a candidate for inlining — the cost of the seam when nobody uses it is a nil check.

func TracerOrNop

func TracerOrNop(t Tracer) Tracer

TracerOrNop turns an unset tracer into the no-op one, so call sites can unconditionally call Start instead of guarding every span with a nil check and getting one of them wrong.

Jump to

Keyboard shortcuts

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