Documentation
¶
Overview ¶
Package web renders isutools measurements: a live report, a self-contained downloadable snapshot.html, machine-readable JSON, and a reset endpoint.
Index ¶
- func NewHandler(p Provider) http.Handler
- func SectionHealth(sections map[string]any) []health.Entry
- type BoundaryProfiler
- type BoundaryWindow
- type Meta
- type ProfileCapture
- type ProfileManifest
- type ProfilePair
- type ProfilePoint
- type Provider
- type RunAbort
- type RunCollectorBoundary
- type RunFinish
- type RunInfo
- type RunSnapshot
- type RunStart
- type Snapshot
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewHandler ¶
NewHandler returns the report handler. Routes are relative: GET / (run index), GET /<run-id> (stored run detail), GET /live, GET /snapshot.html, GET /json, GET /files/<name>, POST /reset, POST /collect, POST /finish, POST /abort, POST /save.
func SectionHealth ¶ added in v1.2.0
SectionHealth derives the health entries a completed run's sections report, one per collector, sorted by collector name.
The baseline collectors carry their notes inside the section value instead of pushing them into a registry, because Collect must be pure. Somebody has to forward them; this is that step, and it is exported so the process-wide registry and this package's renderer forward exactly the same set.
Types ¶
type BoundaryProfiler ¶ added in v1.2.0
type BoundaryProfiler struct {
// contains filtered or unexported fields
}
BoundaryProfiler captures the opening half of a profile pair for a run that was opened outside this package's HTTP surface.
It exists because POST /reset is not the only way a run begins. An application that follows the documented initialize contract opens its runs from its own handler through isutools.ResetNow and may never send a reset at all; without this entry point such a process publishes no opening artifact, and every closing capture finds nothing to pair with and writes nothing.
Both entry points share one process-wide ledger keyed by the data directory, so a run opened here is closed by POST /finish or POST /save exactly as a run opened by POST /reset is, and the two halves are filed under one prefix.
func NewBoundaryProfiler ¶ added in v1.2.0
func NewBoundaryProfiler(p Provider) *BoundaryProfiler
NewBoundaryProfiler returns the capturer for a provider's profile configuration, or nil when the configuration captures nothing — no data directory to publish into, or no profile kinds enabled.
Only DataDir, RuntimeProfiles and Health are read; the capture path touches no collector and serves no request. The kinds are copied, so a caller that later rewrites its own slice cannot change what an opened run captures at its closing boundary.
func (*BoundaryProfiler) CaptureOpen ¶ added in v1.2.0
func (b *BoundaryProfiler) CaptureOpen(run RunStart, generation int64) []string
CaptureOpen writes the opening half of the run's profile pair and returns the artifacts it published.
Call it the moment the coordinator returns the boundary and before the response that releases the benchmarker: the capture is synchronous for the same reason the reset path's is, because a moment chosen by the scheduler is not a boundary.
A nil receiver captures nothing, so a caller never has to branch on whether profiling was configured. Nothing here is fatal.
type BoundaryWindow ¶ added in v1.2.0
type BoundaryWindow struct {
Min time.Time `json:"min,omitzero"`
Max time.Time `json:"max,omitzero"`
Spread time.Duration `json:"spread_ns"`
}
BoundaryWindow is the measured span of one coordinated boundary.
type Meta ¶
type Meta struct {
SchemaVersion int `json:"schema_version"`
Time string `json:"time"`
Generation int64 `json:"generation"`
Revision string `json:"revision"`
Dirty bool `json:"dirty"`
// BuildSource says how Revision was obtained (vcs, ldflags, env, unknown).
// ProvenanceValid is false for an unknown or dirty build, where a snapshot
// cannot be reproduced from Revision alone.
BuildSource string `json:"build_source"`
ProvenanceValid bool `json:"provenance_valid"`
// Score is the benchmark score supplied via POST /save?score=; persisted
// snapshots always carry it so every report is attributable to a result.
Score string `json:"score,omitempty"`
Host sysinfo.Info `json:"host"`
Partial bool `json:"partial"`
Health []health.Entry `json:"health,omitempty"`
Run *RunInfo `json:"run,omitempty"`
// Profiles is the run's runtime-profile record: every capture attempted at
// either boundary and the pairs that can be differenced. It is filled when
// a run is persisted, because that is the first moment both halves exist;
// a live report has no closing half yet and omits the field. Additive and
// omitempty, so a v1.0 reader of this JSON is unaffected.
Profiles *ProfileManifest `json:"profiles,omitempty"`
}
Meta identifies when, on which host, and from which revision a snapshot was taken. Generation increments on every reset so runs are comparable.
type ProfileCapture ¶ added in v1.2.0
type ProfileCapture struct {
RunID string `json:"run_id"`
Epoch uint64 `json:"epoch"`
Point ProfilePoint `json:"point"`
Kind string `json:"kind"`
// File is the published ".pprof" name, empty when nothing was published.
File string `json:"file,omitempty"`
// Sidecar is this record's own file name, so a manifest entry can be
// followed back to the record on disk.
Sidecar string `json:"sidecar"`
OpenGate string `json:"open_gate,omitempty"`
// Orphan marks an artifact whose run never reached a closing boundary. It
// is written into the record rather than left to inference, because a lone
// opening profile is otherwise indistinguishable from half of a pair whose
// other half has not been taken yet.
Orphan bool `json:"orphan,omitempty"`
// The reference point this capture is measured from, copied from the
// coordinator's boundary record. A boundary is an interval rather than an
// instant, so the measured spreads travel with it and form the uncertainty
// floor under every residual below. Legacy providers leave them zero, which
// means "not reported", not "measured as zero".
RefPhase string `json:"ref_phase"`
RefAt time.Time `json:"ref_at,omitzero"`
RefSpreadNs int64 `json:"ref_spread_ns"`
BoundaryAt time.Time `json:"boundary_at,omitzero"`
BoundarySpreadNs int64 `json:"boundary_spread_ns"`
// The measured capture instants. LagFromRefNs is the distance from the
// boundary to the moment the write actually began, which is the number the
// residual error is built out of.
StartedAt time.Time `json:"started_at,omitzero"`
FinishedAt time.Time `json:"finished_at,omitzero"`
LagFromRefNs int64 `json:"lag_from_ref_ns"`
DurationNs int64 `json:"duration_ns"`
Bytes int64 `json:"bytes"`
Status string `json:"status"`
Code string `json:"code,omitempty"`
Err string `json:"err,omitempty"`
}
ProfileCapture is one profile file's record: what was captured, when, and how far after the boundary it claims to bracket. It is the content of the ".meta.json" sidecar written next to every artifact.
The sidecar is the durable primary record. An opening capture is written at reset, long before any snapshot exists to hold a manifest, so the sidecar is the only place the opening moment can survive a process that never finishes the run.
type ProfileManifest ¶ added in v1.2.0
type ProfileManifest struct {
RunID string `json:"run_id"`
Epoch uint64 `json:"epoch"`
Validity string `json:"validity,omitempty"`
Captures []ProfileCapture `json:"captures,omitempty"`
// Pairs holds only the kinds whose two halves both exist. A kind with one
// half is deliberately absent: a difference cannot be taken from it, and
// listing it would invite the reader to try.
Pairs []ProfilePair `json:"pairs,omitempty"`
}
ProfileManifest is a run's whole profile record: every capture attempted at either boundary, and the pairs that can actually be differenced.
type ProfilePair ¶ added in v1.2.0
type ProfilePair struct {
Kind string `json:"kind"`
OpenFile string `json:"open_file"`
CloseFile string `json:"close_file"`
OpenGate string `json:"open_gate,omitempty"`
// RunSpanNs is the distance between the two boundaries themselves.
RunSpanNs int64 `json:"run_span_ns"`
// HeadLossNs is the run's beginning that the difference does not contain.
HeadLossNs int64 `json:"head_loss_ns"`
// TailExcessNs is the post-boundary tail that the difference does contain.
TailExcessNs int64 `json:"tail_excess_ns"`
// ApproxErrorNs is HeadLossNs + TailExcessNs: the total by which the
// difference is not the run.
ApproxErrorNs int64 `json:"approx_error_ns"`
DiffCommand string `json:"diff_command"`
}
ProfilePair is one kind's difference over a run: the two artifacts plus the measured error in treating their difference as the run.
It is an approximation and says so. The difference starts a little after the run does (HeadLossNs, not included) and ends a little after the run ends (TailExcessNs, included), because both halves are taken by the caller after the coordinator has already fixed the boundary.
func (ProfilePair) Lagging ¶ added in v1.2.0
func (p ProfilePair) Lagging() bool
Lagging reports whether either half of the pair was taken too far from the boundary it names. The Runs detail page and the health verdict both call this, so the badge on the page and the entry in health can never disagree.
func (ProfilePair) Notes ¶ added in v1.2.0
func (p ProfilePair) Notes() []string
Notes returns the sentences the Runs detail page prints under a pair.
The approximation notice is unconditional. A small residual is still a residual, and a reader who sees the notice only on bad runs will read its absence as "this one is exact", which no pair ever is.
func (ProfilePair) ResidualText ¶ added in v1.2.0
func (p ProfilePair) ResidualText() string
ResidualText renders the pair's residual error for the Runs detail page.
type ProfilePoint ¶ added in v1.2.0
type ProfilePoint string
ProfilePoint names the run boundary a runtime profile was captured at. A mutex, block or heap profile is process-wide and cumulative, so a single file says nothing about a run: only the difference between the two boundaries does, and the point is what tells them apart.
const ( // ProfilePointOpen is the capture taken as the run opens. ProfilePointOpen ProfilePoint = "open" // ProfilePointClose is the capture taken as the run's boundary is frozen. ProfilePointClose ProfilePoint = "close" )
type Provider ¶
type Provider struct {
SQL sqlSnapshotter
// SQLGeneration and RotateSQL opt into atomic generation boundaries. They
// are separate callbacks so simple aggregation tables remain usable in
// tests and custom integrations.
SQLGeneration func() int64
RotateSQL func() (generation int64, entries []agg.Entry)
// RunGenerationManaged is shorthand for custom providers whose SQL, HTTP,
// and Counters generations are all owned by StartRun. The per-collector
// flags below let production preserve that guarantee when only a subset
// registered successfully.
RunGenerationManaged bool
SQLGenerationManaged bool
HTTPGenerationManaged bool
CountersGenerationManaged bool
Health *health.Registry
HTTP httpCollector
AccessLog accessLogCollector
AccessLogQuiet time.Duration
AccessLogPoll time.Duration
// AccessLogGenerationManaged says the access log's generation adapter is
// registered with the run coordinator, so the coordinator's
// BeginBoundary → Drain → Release cycle owns the aggregate's lifetime.
//
// POST /reset must then keep its hands off the legacy Snapshot()+Reset()
// pair. Reset re-opens the log at the current end of file, drops the
// aggregate and zeroes the health counters the drain's file-replacement
// guard reads; running it between a closing boundary and that boundary's
// drain leaves the drain nothing to seal, and the finished run's
// access-log section silently comes out empty.
AccessLogGenerationManaged bool
CollectTimeout time.Duration
// InspectionTimeout bounds the context passed to DB and Advisor callbacks.
InspectionTimeout time.Duration
Proc processCollector
// ProcRunManaged says Proc is registered with the run coordinator. Its
// reset and final snapshot then happen inside StartRun/FinishRun for both
// the HTTP and embedded ResetNow entry points.
ProcRunManaged bool
// DB captures the database schema (tables/indexes). Called at handler
// startup and on every reset so each generation records the pre-run state.
DB func(context.Context) *dbinspect.Schema
// Advisor reports well-known settings that are not configured. Captured
// alongside the DB schema at startup and on every reset.
Advisor func(context.Context) []advisor.Check
// CacheTelemetry is evaluated at snapshot time so application cache
// hit/miss/eviction counters can match the measured interval.
CacheTelemetry func() (*advisor.CacheTelemetry, error)
// QUICTelemetry is evaluated at snapshot time so packet counters can match
// the completed benchmark interval rather than handler startup.
QUICTelemetry func() (*advisor.QUICTelemetry, error)
// ProtocolTrafficClientFacing is false when a CDN/LB terminates the client
// connection before the locally collected access log.
ProtocolTrafficClientFacing *bool
// Counters exposes user-defined counters (isutools.Count). Reset per
// generation.
Counters interface {
Snapshot() []counters.Entry
Reset()
}
// DataDir persists snapshots for the dashboard history ("" = disabled).
DataDir string
// PprofDuration > 0 captures a CPU profile for that long after every
// reset (i.e. covering the benchmark), stored in DataDir (0 = disabled).
PprofDuration time.Duration
// StartRun opens a measurement run at the reset boundary and names it in
// the reset response. Nil keeps the legacy behaviour, in which a reset
// only rotates the collector generations and no run id exists.
//
// A failure is not fatal to the reset: the generations have already been
// rotated by the time it is called, and refusing to answer would leave the
// bench script unable to proceed with measurements that are, in fact,
// running.
StartRun func(ctx context.Context) (RunStart, error)
// FinishRun fixes the closing boundary of the run in flight and returns as
// soon as that boundary exists. POST /finish calls it and answers 202:
// draining and snapshot building continue in the background, and making
// the caller wait for them would put snapshot-building time inside the
// measured window of whatever runs next.
//
// Nil leaves POST /finish unavailable, which is the legacy behaviour of a
// transport wired without a run coordinator.
FinishRun func(ctx context.Context) (RunFinish, error)
// CompleteRun finishes the run in flight, waits for its immutable
// snapshot, and acknowledges it. POST /save calls it before rendering, so
// the persisted report describes the interval the run measured rather than
// whatever the live collectors happen to hold after their generations were
// frozen.
//
// A process that never opened a run must report the zero RunFinish and no
// error: /save predates the run lifecycle and has to keep working without
// one.
CompleteRun func(ctx context.Context) (RunFinish, error)
// AbortRun abandons the run in flight and fences its background worker.
// POST /abort is idempotent and publishes no snapshot; profile artifacts
// already captured at the opening boundary are marked as orphans.
AbortRun func(ctx context.Context) (RunAbort, error)
// Sections supplies the completed run's collector sections keyed by
// collector name, as produced by the run coordinator. Unknown keys and
// unexpected types are ignored, so a collector can be added on one side of
// the wiring before the other catches up.
Sections func() map[string]any
// RunSnapshot supplies the same sections together with the lifecycle
// evidence that makes a completed run auditable. When set it supersedes
// Sections; the older callback remains for custom-provider compatibility.
RunSnapshot func() *RunSnapshot
// RuntimeProfiles lists the runtime profiles ("mutex", "block", "heap")
// captured at a run boundary, in capture order. Empty — the default —
// captures nothing. The rates themselves are process-wide runtime
// settings owned by the caller; this package only writes what it is told
// is enabled, so a profile whose rate is zero must not appear here.
RuntimeProfiles []string
}
Provider supplies the collectors to render. Nil fields are skipped.
type RunAbort ¶ added in v1.2.0
type RunAbort struct {
RunID string `json:"run_id,omitempty"`
Epoch uint64 `json:"epoch,omitempty"`
Reason string `json:"reason,omitempty"`
Detached bool `json:"detached,omitempty"`
AbortedAt time.Time `json:"aborted_at,omitzero"`
Partial []string `json:"partial,omitempty"`
}
RunAbort is the transport projection of the coordinator's abort result. POST /abort itself answers 204, but the handler needs this record to orphan the right profile pair and report the run id back in its header.
type RunCollectorBoundary ¶ added in v1.2.0
type RunCollectorBoundary struct {
Name string `json:"name"`
Kind string `json:"kind"`
Required bool `json:"required"`
Phase string `json:"phase"`
At time.Time `json:"at,omitzero"`
Committed bool `json:"committed"`
Code string `json:"code,omitempty"`
Err string `json:"err,omitempty"`
Dropped bool `json:"dropped,omitempty"`
}
RunCollectorBoundary records one collector operation at a run boundary.
type RunFinish ¶ added in v1.2.0
type RunFinish struct {
// RunID names the run whose boundary was fixed. Empty means no run was in
// flight, which is not an error: the save/collect loop predates runs.
RunID string `json:"run_id,omitempty"`
// Epoch is the coordinator fencing token for this closing boundary.
Epoch uint64 `json:"epoch,omitempty"`
// Validity is the run's data-quality verdict ("valid", "partial",
// "invalid"), copied verbatim like RunStart.Validity.
Validity string `json:"validity,omitempty"`
// AcceptedAt is the measured moment the closing boundary was fixed. It is
// the end of the interval every section of this run describes.
AcceptedAt time.Time `json:"accepted_at,omitzero"`
// GenerationWindow and BoundaryWindow preserve the coordinator's measured
// uncertainty at the closing boundary for profile residual accounting.
GenerationWindow BoundaryWindow `json:"generation_window,omitzero"`
BoundaryWindow BoundaryWindow `json:"boundary_window,omitzero"`
}
RunFinish is the record of a run's closing boundary, as reported by Provider.FinishRun and Provider.CompleteRun.
type RunInfo ¶ added in v1.2.0
type RunInfo struct {
RunID string `json:"run_id"`
Epoch uint64 `json:"epoch"`
Validity string `json:"validity"`
Trigger string `json:"trigger,omitempty"`
Collectors []RunCollectorBoundary `json:"collectors,omitempty"`
GenerationWindow BoundaryWindow `json:"generation_window"`
BoundaryWindow BoundaryWindow `json:"boundary_window"`
StartedAt time.Time `json:"started_at,omitzero"`
FinishedAt time.Time `json:"finished_at,omitzero"`
}
RunInfo is the immutable lifecycle envelope persisted with a report.
type RunSnapshot ¶ added in v1.2.0
RunSnapshot is the provider-side form of a completed run. Sections remain top-level in the report while Info is persisted under Meta.Run.
type RunStart ¶ added in v1.2.0
type RunStart struct {
// RunID is the coordinator's run identifier, echoed in the reset response
// so a bench script can name the run it started.
RunID string
// Epoch is the coordinator fencing token. It prevents a delayed boundary
// artifact from being attached to a newer incarnation of the same run id.
Epoch uint64
// StartedAt is the opening boundary's moment. Boundary artifacts are named
// after it rather than after the moment they are written, so an opening
// and a closing artifact of one run share a filename prefix.
StartedAt time.Time
// Validity is the run's data-quality verdict ("valid", "partial",
// "invalid"). It is a plain string because the transport copies it
// verbatim and never branches on it.
Validity string
// GenerationWindow and BoundaryWindow preserve the coordinator's measured
// uncertainty at the opening boundary for profile residual accounting.
GenerationWindow BoundaryWindow
BoundaryWindow BoundaryWindow
}
RunStart identifies the measurement run a reset opened.
type Snapshot ¶
type Snapshot struct {
Meta Meta `json:"meta"`
DB *dbinspect.Schema `json:"db,omitempty"`
Advisor []advisor.Check `json:"advisor,omitempty"`
Counters []counters.Entry `json:"counters,omitempty"`
Connections *httpstats.ConnSnapshot `json:"connections,omitempty"`
SQL []agg.Entry `json:"sql"`
HTTP httpstats.Snapshot `json:"http,omitempty"`
AccessLog *accesslog.Snapshot `json:"accesslog,omitempty"`
Proc *procstats.Snapshot `json:"proc,omitempty"`
// The sections below come from the run coordinator's baseline collectors
// rather than from a live collector read, so they describe the interval
// between two run boundaries and are absent until a run has completed.
// Every one of them is additive and omitempty: a v1.0 reader of this JSON
// is unaffected by their presence.
//
// The JSON keys are the collector names the coordinator registers under,
// so a section can be traced from the snapshot back to the collector that
// filled it without a translation table.
Host *hoststats.Section `json:"hoststats,omitempty"`
Network *netstats.NetworkStats `json:"network,omitempty"`
SQLRows *sqlrows.Section `json:"sqlrows,omitempty"`
DBPool []dbpool.Entry `json:"dbpool,omitempty"`
// QueryPlan holds the EXPLAIN output captured in the run's enrich phase.
// It is filled from the same section map as the others rather than from a
// live read, which is what keeps a dashboard refresh from putting EXPLAIN
// statements on the measured database.
QueryPlan *queryplan.Section `json:"queryplan,omitempty"`
}
Snapshot is the complete state of all measurements at one point in time.