httpstats

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package httpstats provides bounded, in-memory HTTP request measurements. It deliberately records URL paths, never query strings.

Index

Constants

View Source
const (
	// DefaultMaxKeys is the maximum number of distinct HTTP identities held by
	// a Collector. New identities past the limit are merged into OverflowPath.
	DefaultMaxKeys = 10000
	// OverflowPath identifies observations merged after the key limit.
	OverflowPath = "(other)"
)
View Source
const (
	ProfileRouteUnmatched            = "(unmatched)"
	ProfileMethodOther               = "OTHER"
	MaxProfileRouteBytes             = 128
	MaxSafeRouteRulesSpecBytes       = 16 << 10
	MaxSafeRouteRules                = 64
	MaxSafeRouteRuleRegexpBytes      = 256
	MaxSafeRouteRuleRegexpTotalBytes = 8 << 10
)
View Source
const CollectorName = "httpstats"

CollectorName is the snapshot section this collector fills.

View Source
const ResetDrainBudget = runctl.DrainBudget

ResetDrainBudget bounds how long the Reset compatibility shim waits for the requests pinned to the generation it closes.

It is runctl.DrainBudget because Reset is exactly the drain step of a run boundary expressed through the pre-generation API, and the two must not be able to disagree about how long a wedged request is worth waiting for.

View Source
const RouteNotFound = "/{not-found}"

Variables

View Source
var Default = New()

Default is used by the package-level Middleware helper.

Functions

func Middleware

func Middleware(next http.Handler) http.Handler

Middleware instruments next using Default.

func ProfileLabelMiddleware added in v1.4.0

func ProfileLabelMiddleware(next http.Handler, labeler ProfileLabeler, rules []SafeProfileRouteRule) http.Handler

func SetRouteNotFound added in v1.5.0

func SetRouteNotFound(request *http.Request) bool

SetRouteNotFound installs a constant identity for routers that report no registered template after routing. It prevents a secret slug in URL.Path from becoming a 404 aggregate key.

func SetRoutePattern added in v1.5.0

func SetRoutePattern(request *http.Request, pattern string) bool

SetRoutePattern is the framework-neutral route adapter contract. Framework middleware calls it after routing with the registered template, never the expanded URL. The outer Collector.Middleware reads the same request after the handler returns.

Types

type Collector

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

Collector owns HTTP measurements and generation boundaries. Reset swaps in a fresh table and waits for requests already in flight, then returns their completed generation; the same mechanism backs the runctl.GenerationCollector implementation in generation.go.

Lock order: mu may be held while acquiring connMu, never the reverse.

func New

func New(opts ...Option) *Collector

New returns an independent Collector.

func (*Collector) BeginBoundary added in v1.2.0

func (c *Collector) BeginBoundary(ctx context.Context, runID string, ep runctl.Epoch) (runctl.BoundaryResult, error)

BeginBoundary swaps in a fresh generation and returns a handle to the one it just closed. It only moves a pointer, so it does not block on in-flight requests.

func (*Collector) Collect added in v1.2.0

func (c *Collector) Collect(h runctl.GenerationHandle) (any, error)

Collect reads the fixed data of a drained generation. It never reads the collector's current generation.

func (*Collector) Connections added in v0.7.0

func (c *Collector) Connections() ConnSnapshot

Connections reports long-lived connection stats for the current generation.

func (*Collector) Drain added in v1.2.0

Drain waits for the requests pinned to the handle's generation to finish. It returns as soon as ctx is done, leaving no goroutine that touches any other generation: a late request writes to its own sealed table and nowhere else.

func (*Collector) Freeze added in v1.2.0

func (c *Collector) Freeze(ctx context.Context, runID string, ep runctl.Epoch) (runctl.BoundaryResult, error)

Freeze seals the current generation and returns its handle. Requests that start after Freeze are pinned to the next generation and stay outside the run.

func (*Collector) Middleware

func (c *Collector) Middleware(next http.Handler) http.Handler

Middleware returns a handler that measures method, normalized path, protocol, status, duration, and response body bytes.

func (*Collector) Name added in v1.2.0

func (c *Collector) Name() string

Name identifies the snapshot section this collector fills.

func (*Collector) Release added in v1.2.0

func (c *Collector) Release(h runctl.GenerationHandle)

Release frees the table the handle pins. It is idempotent, and a request that arrives late from a drain timeout still finds a usable (empty) table rather than a nil map, because measurement may not panic into the application.

func (*Collector) Reset

func (c *Collector) Reset() Snapshot

Reset atomically starts a new generation, waits for requests that started in the old one to finish, and returns the completed old generation. It is a compatibility shim over the generation mechanism: the same per-generation done channel Drain uses.

The wait is bounded by ResetDrainBudget, or by whatever SetResetDrainBudget installed. An unbounded wait here would reintroduce on the /reset path exactly what the done channel exists to prevent: /reset calls Reset while it holds the process-wide reset lock and the operation slot, so one request that never returns — a hijacked connection nobody confirmed, a handler parked on a wedged dependency — would wedge /reset and, through the same lock, /finish, /collect and /save, for the life of the process.

When the budget expires the generation's table is returned as it stands: a usable, partial snapshot, because a late request writes only to its own sealed table and never to the live one. The cut is counted so the caller can read it back through ResetsCutShort and mark its section partial.

func (*Collector) ResetsCutShort added in v1.2.0

func (c *Collector) ResetsCutShort() int64

ResetsCutShort reports how many Reset calls returned on their budget with requests still pinned to the generation they closed. A non-zero value means at least one returned snapshot was missing requests that were still running, which is a partial section rather than a failed one.

func (*Collector) SetEventObserver added in v1.4.0

func (c *Collector) SetEventObserver(observer EventObserver)

SetEventObserver replaces the optional event observer. Passing nil disables event delivery. The observer is process-local and is never serialized.

func (*Collector) SetEventRouteRules added in v1.4.0

func (c *Collector) SetEventRouteRules(rules []SafeProfileRouteRule)

SetEventRouteRules installs full-match-to-constant rules for event labels. They are separate from legacy aggregation rules, whose replacements may retain captures and therefore cannot prove non-secret output.

func (*Collector) SetResetDrainBudget added in v1.2.0

func (c *Collector) SetResetDrainBudget(budget time.Duration)

SetResetDrainBudget bounds the wait performed by Reset. A non-positive value restores ResetDrainBudget. It exists for callers whose own operation budget is tighter than the run controller's; the default is already bounded, so leaving it alone is safe.

func (*Collector) SetRules added in v0.7.0

func (c *Collector) SetRules(rules []Rule)

SetRules replaces the collector's path rules at runtime (e.g. from the ISUTOOLS_PATH_RULES environment variable).

func (*Collector) Snapshot

func (c *Collector) Snapshot() Snapshot

Snapshot returns the currently active generation without clearing it.

type ConnSnapshot added in v0.7.0

type ConnSnapshot struct {
	Total        int64   `json:"total"`
	Active       int64   `json:"active"`
	AvgSeconds   float64 `json:"avg_seconds"`
	P95Seconds   float64 `json:"p95_seconds"`
	MaxSeconds   float64 `json:"max_seconds"`
	BytesRead    int64   `json:"bytes_read"`
	BytesWritten int64   `json:"bytes_written"`
}

ConnSnapshot summarizes long-lived connections (WebSocket upgrades and text/event-stream responses) which are excluded from the latency table so they cannot distort p95/avg. Active spans generations; the rest reset with the generation.

type Entry

type Entry struct {
	Key        string        `json:"key"`
	Method     string        `json:"method"`
	Path       string        `json:"path"`
	Protocol   string        `json:"protocol"`
	Status     int           `json:"status"`
	Count      int64         `json:"count"`
	Total      time.Duration `json:"total_ns"`
	Avg        time.Duration `json:"avg_ns"`
	Max        time.Duration `json:"max_ns"`
	P95        time.Duration `json:"p95_ns"`
	TotalBytes int64         `json:"total_bytes"`
	AvgBytes   int64         `json:"avg_bytes"`
}

Entry is one aggregated HTTP identity. Duration fields are encoded as integer nanoseconds in JSON. P95 is the upper bound of a log2 bucket.

type EventObserver added in v1.4.0

type EventObserver interface {
	HTTPStart(time.Time) any
	HTTPFinish(time.Time, any, string, time.Duration, bool)
	HTTPCancel(time.Time, any)
}

EventObserver receives bounded, already-sanitized request events for run-aligned consumers such as the optional timeline. Observer failures are isolated from the application request path.

type Option

type Option func(*config)

Option configures a Collector.

func WithMaxKeys

func WithMaxKeys(max int) Option

WithMaxKeys changes the distinct-key limit. A value of zero merges every observation into the overflow entry. Negative values are ignored.

func WithPathRules

func WithPathRules(rules []Rule) Option

WithPathRules replaces the built-in path normalization with rules applied in order. Rules with a nil Pattern are ignored.

type ProfileLabel added in v1.4.0

type ProfileLabel struct {
	Method string
	Route  string
}

ProfileLabel is intentionally narrower than the dashboard aggregation key. Route can originate only from router metadata installed by trusted code; it never uses URL.Path or heuristic normalization, both of which may contain user identifiers, reset tokens, invite slugs, or other secrets.

func SafeProfileLabel added in v1.4.0

func SafeProfileLabel(request *http.Request) ProfileLabel

func SafeProfileLabelWithRules added in v1.4.0

func SafeProfileLabelWithRules(request *http.Request, rules []SafeProfileRouteRule) ProfileLabel

type ProfileLabeler added in v1.4.0

type ProfileLabeler interface {
	DoProfileLabels(context.Context, ProfileLabel, func(context.Context)) bool
}

type Result added in v1.2.0

type Result struct {
	HTTP        Snapshot     `json:"http"`
	Connections ConnSnapshot `json:"connections"`
}

Result is the fixed data of one drained generation: the latency table plus the long-lived connection totals accumulated in the same window.

type Rule

type Rule struct {
	Pattern     *regexp.Regexp
	Replacement string
}

Rule replaces matching text in a request path. Supplying path rules disables the built-in numeric and UUID segment normalization.

func ParseRules added in v0.7.0

func ParseRules(spec string) ([]Rule, error)

ParseRules parses an ISUTOOLS_PATH_RULES spec: semicolon-separated "regex=replacement" pairs, split on the LAST '=' so regexes may contain '='. Example: "^/@[^/]+$=/@*;^/posts/[0-9]+$=/posts/*".

type SafeProfileRouteRule added in v1.4.0

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

func ParseSafeProfileRouteRules added in v1.4.0

func ParseSafeProfileRouteRules(spec string) ([]SafeProfileRouteRule, error)

ParseSafeProfileRouteRules accepts only full-match regexes with constant, non-secret replacements. Captures may be used for matching but are never expanded into the emitted route label.

type Snapshot

type Snapshot []Entry

Snapshot is a point-in-time copy sorted by total duration descending.

Jump to

Keyboard shortcuts

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