Documentation
¶
Overview ¶
Package httpstats provides bounded, in-memory HTTP request measurements. It deliberately records URL paths, never query strings.
Index ¶
- Constants
- Variables
- func Middleware(next http.Handler) http.Handler
- func ProfileLabelMiddleware(next http.Handler, labeler ProfileLabeler, rules []SafeProfileRouteRule) http.Handler
- type Collector
- func (c *Collector) BeginBoundary(ctx context.Context, runID string, ep runctl.Epoch) (runctl.BoundaryResult, error)
- func (c *Collector) Collect(h runctl.GenerationHandle) (any, error)
- func (c *Collector) Connections() ConnSnapshot
- func (c *Collector) Drain(ctx context.Context, h runctl.GenerationHandle) error
- func (c *Collector) Freeze(ctx context.Context, runID string, ep runctl.Epoch) (runctl.BoundaryResult, error)
- func (c *Collector) Middleware(next http.Handler) http.Handler
- func (c *Collector) Name() string
- func (c *Collector) Release(h runctl.GenerationHandle)
- func (c *Collector) Reset() Snapshot
- func (c *Collector) ResetsCutShort() int64
- func (c *Collector) SetEventObserver(observer EventObserver)
- func (c *Collector) SetEventRouteRules(rules []SafeProfileRouteRule)
- func (c *Collector) SetResetDrainBudget(budget time.Duration)
- func (c *Collector) SetRules(rules []Rule)
- func (c *Collector) Snapshot() Snapshot
- type ConnSnapshot
- type Entry
- type EventObserver
- type Option
- type ProfileLabel
- type ProfileLabeler
- type Result
- type Rule
- type SafeProfileRouteRule
- type Snapshot
Constants ¶
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)" )
const ( ProfileRouteUnmatched = "(unmatched)" ProfileMethodOther = "OTHER" MaxProfileRouteBytes = 128 MaxSafeRouteRulesSpecBytes = 16 << 10 MaxSafeRouteRules = 64 MaxSafeRouteRuleRegexpBytes = 256 MaxSafeRouteRuleRegexpTotalBytes = 8 << 10 )
const CollectorName = "httpstats"
CollectorName is the snapshot section this collector fills.
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.
Variables ¶
var Default = New()
Default is used by the package-level Middleware helper.
Functions ¶
func Middleware ¶
Middleware instruments next using Default.
func ProfileLabelMiddleware ¶ added in v1.4.0
func ProfileLabelMiddleware(next http.Handler, labeler ProfileLabeler, rules []SafeProfileRouteRule) http.Handler
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 (*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 ¶
Middleware returns a handler that measures method, normalized path, protocol, status, duration, and response body bytes.
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 ¶
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
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
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.
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 ¶
WithMaxKeys changes the distinct-key limit. A value of zero merges every observation into the overflow entry. Negative values are ignored.
func WithPathRules ¶
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
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 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 ¶
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
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.