profile

package
v0.5.5 Latest Latest
Warning

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

Go to latest
Published: Jun 9, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultHeapSampleCap = 1024

DefaultHeapSampleCap is the per-request ceiling on retained heap samples. Wasm memory grows in 64KB pages, so 1024 *distinct* samples implies ~64MB of growth — well outside what a normal request should produce. The cap is a safety valve against pathological guests, not a tunable.

View Source
const (
	DefaultProfileSpanCap = 8000
)

Variables

View Source
var ErrNoNativeSamples = errors.New("no native samples in input")

ErrNoNativeSamples is returned by NativeSampleImporter implementations when the input parsed cleanly but contained no samples. It is a sentinel value comparable with errors.Is.

View Source
var ErrProfileUIAuthMissing = errors.New("profile UI auth required for non-loopback bind")

ErrProfileUIAuthMissing is returned by ValidateProfileUIAuth when the supplied bind/auth combination violates the security policy. The error message names the flag the operator needs to add so the failure mode is self-documenting.

Functions

func EncodeChromeTrace

func EncodeChromeTrace(t *RequestTrace) ([]byte, error)

EncodeChromeTrace renders a RequestTrace into the Chrome Tracing JSON Object Format described at https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/ (the "Trace Event Format" doc). The output is loadable directly in `about:tracing` and in Perfetto (https://ui.perfetto.dev/).

The mapping is deliberately minimal:

  • One process (pid=1) called "fastlike-request".
  • Three tids: 1=hostcalls, 2=backends, 3=native.
  • Each Span → ph="X" complete event on tid=1.
  • Each BackendCall → ph="X" complete event on tid=2 with phase fields as args; nil pointers are omitted from args.
  • Each NativeSample → ph="i" instant event on tid=3.
  • Header-flush timestamp → ph="i" instant on tid=1 named "header_flush" (scope="g" so the marker shows on every track).
  • Hijack timestamp → ph="i" instant on tid=1 named "hijack".
  • Dropped + DroppedBackendCalls → ph="i" instants at t=0 named "dropped_spans" / "dropped_backend_calls" so the viewer can't silently render a truncated trace as a complete one.
  • Trace outcome → ph="i" instant at t=0 named "outcome.<value>".

The encoder is pure: no store or UI dependency, no I/O. The output is canonical JSON; goldens compare against the pretty-printed form.

Privacy: URL fields use URLRedacted from the in-memory trace, which already strips userinfo and query strings outside deep mode. Hostcall tag slots are passed through as opaque integers (their semantics are hostcall-specific and documented in trace_schema.md, not unwrapped here). No header values, no body bytes, no secrets reach this JSON.

func EncodeFirefoxGecko

func EncodeFirefoxGecko(t *RequestTrace) ([]byte, error)

EncodeFirefoxGecko renders a RequestTrace into the Firefox profiler "Gecko" profile format. The output is loadable at https://profiler.firefox.com/ via "Load a profile from file".

The schema fastlike emits is a v27 Gecko profile with a single thread carrying our spans and backend calls as markers and the native samples as time samples backed by a tiny frame/func/string table. This is a deliberately minimal mapping: Firefox's full profile shape supports stacks, categories, marker schemas, JS allocations, screenshots, and other dimensions fastlike has no signal for. The format we emit covers the timeline view (the only thing a Firefox profiler user wants from a wall-time hostcall trace) without inventing facts.

Schema reference:

https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md

Privacy: the encoder only consumes fields that already survived the privacy filter (URLRedacted, resolved hostcall names, backend names). Header values, body bytes, secrets, and URL query strings are physically inaccessible from the in-memory trace, so the format cannot leak them.

func EncodePprof

func EncodePprof(t *RequestTrace) ([]byte, error)

EncodePprof renders a RequestTrace into the gzip-compressed profile.proto format consumable by `go tool pprof` and the pprof web UI.

The mapping is intentionally synthetic: pprof is designed for statistical CPU profiling, while a RequestTrace is a per-request wall-time event log. To bridge the impedance mismatch, fastlike emits one Sample per hostcall span and one Sample per backend call. Each sample's value is the span's wall-time duration in nanoseconds, and each Sample carries a Location naming the hostcall or backend. The user reads this as "this is how long was spent in each subsystem during this request", which is the load-bearing question pprof's flame / cumulative views answer well.

Native samples (when present) become per-sample entries with value 1 and a "native sample" sample type, so they appear as counts rather than durations — that matches what an external profiler actually captured (a sample at a moment in time, not a duration).

The output is the gzip-compressed protobuf wire form pprof expects. Golden tests decode the bytes back through profile.Parse before comparing structure; the raw wire form is not stable across protobuf encoder versions and is the wrong layer to assert on.

Privacy: the encoder only references URLRedacted, hostcall names, backend names, and outcomes — all fields the in-memory trace already vetted. No header values, body bytes, secrets, or userinfo can reach this output.

func HostcallNameIndex

func HostcallNameIndex(name string) uint16

func IsLoopbackBindAddress

func IsLoopbackBindAddress(addr string) bool

IsLoopbackBindAddress reports whether addr is a loopback bind for purposes of the UI auth gate. The check happens at config time, not on every request: the operator picked the address up-front and we want one authoritative answer about whether bearer auth is required.

Returns true only for:

  • any IP literal in 127.0.0.0/8
  • "::1"
  • the literal hostname "localhost" (case-insensitive)

Wildcard binds (":<port>", "0.0.0.0", "[::]") and any other hostname require -profile-auth or -profile-insecure-ui. Resolving arbitrary hostnames is intentionally NOT supported: the auth gate must not depend on resolver state at startup, which can disagree with whatever net.Listen ultimately binds. Unix-socket paths are not accepted either — the profile UI binds via net.Listen("tcp", ...) and a path-shaped addr would just crash the process at bind time.

func MergeNativeSamples

func MergeNativeSamples(store *ProfileStore, events []NativeSampleEvent, expectedPID int, expectedModuleID string) int

MergeNativeSamples distributes events across the completed traces in store. Each event attaches to at most one trace, chosen by:

  • PID match: events.PID must equal expectedPID. The CLI sets expectedPID to os.Getpid() so samples leaking from other processes sharing the same input file are filtered out.
  • Module match: when expectedModuleID is non-empty, only traces whose ModuleID equals it receive samples. This protects against samples captured before a hot reload landing on post-reload traces with a different module fingerprint.
  • Time window: the sample's UnixNanos must fall within [trace.WallStart, trace.WallStart + trace.WallNanos]. A sample before the trace started or after it finalized is dropped.

Drops are silent: a sample that does not match any trace is not an error. The function returns the number of samples actually attached so the CLI can print a single summary line.

Merge is store-wide; the caller is expected to pass the complete output of a profiling run rather than per-trace slices, because the profiler does not know which sample belongs to which request.

func ModuleIDOf

func ModuleIDOf(wasmbytes []byte) string

ModuleIDOf returns a short stable identifier for wasmbytes, formed from the first eight bytes of its SHA-256 digest. Used to tag traces with the module version they ran against so reloads do not silently merge before/after data.

func NativeProfilerStrategy

func NativeProfilerStrategy(mode ProfileMode) (wasmtime.ProfilingStrategy, bool)

NativeProfilerStrategy maps a ProfileMode to the wasmtime profiling strategy fastlike should configure on the engine. Returns ProfilingStrategyNone unless mode is native or combined, and unless the host platform actually supports jitdump (today: Linux).

This helper is the single source of truth for the platform gate so tests can pin the mapping without standing up a real wasmtime engine or requiring perf/samply to be installed. Callers separately decide whether to log a notice when the configured mode would have enabled sampling but the platform does not support it.

func NativeSamplingRequested

func NativeSamplingRequested(mode ProfileMode) bool

NativeSamplingRequested reports whether the configured mode asked for native sampling, regardless of whether the platform supports it. The CLI uses this to print a one-line notice when a native/combined mode is configured on a host with no supported strategy, so the operator understands why their sampler will see no jitdump output.

func ResolveHostcallName

func ResolveHostcallName(idx uint16) string

ResolveHostcallName looks up the interned name. Out-of-range indices fall back to the sentinel so a forward-compatible reader never panics on a trace produced by a newer fastlike with new hostcalls in the table.

func ValidateProfileUIAuth

func ValidateProfileUIAuth(addr, token string, insecure bool) error

ValidateProfileUIAuth enforces the security policy from plans/guest-profiling.md: loopback binds need no auth, non-loopback binds require either a bearer token or the explicit insecure-ui escape hatch. The caller (CLI startup, embedder bootstrap) should hard-exit on a non-nil error.

func WrapProfileUIAuth

func WrapProfileUIAuth(h http.Handler, token string) http.Handler

WrapProfileUIAuth wraps h with a bearer-token check when token is non-empty. The middleware enforces the token on every request including the index, JSON endpoints, and any future SSE stream. When token is empty the handler is returned unwrapped.

The constant-time comparison avoids the timing oracle a naive `==` would create, even though the token is configured by the operator rather than the network.

func WriteWasmSymbolSidecar

func WriteWasmSymbolSidecar(wasmbytes []byte, dir, moduleID string, mode ProfileMode) (string, error)

WriteWasmSymbolSidecar writes wasm-symbols-{pid}.json into dir (or the current working directory when dir is empty) containing the export list of the module compiled from wasmbytes. Returns the file path on success.

The emission is best-effort: a failure to extract exports or write the file logs and returns the error, but does not abort startup. The sidecar is a debugging aid for external samplers, not load-bearing for the in-process trace, so a missing sidecar must never break the rest of the profiler.

Callers (New, Reload) gate emission on NativeSamplingRequested(mode) so trace-only / off configurations skip this entirely.

Types

type BackendCall

type BackendCall struct {
	PendingID       uint32
	Name            string
	Method          string
	URLRedacted     string
	Started         int64
	DNSNanos        *int64
	ConnectNanos    *int64
	TLSNanos        *int64
	TTFBNanos       *int64
	TotalNanos      int64
	Status          int
	ReqHeaderBytes  int
	RespHeaderBytes int
	Outcome         BackendOutcome
}

BackendCall is one round trip to a backend handler.

type BackendOutcome

type BackendOutcome uint8

BackendOutcome categorises how a backend call resolved.

const (
	BackendOutcomeOk BackendOutcome = iota
	BackendOutcomeNetworkError
	BackendOutcomeSyntheticFailure
	BackendOutcomeCancelled
	BackendOutcomeIncomplete
	BackendOutcomeOrphaned
)

func (BackendOutcome) String

func (o BackendOutcome) String() string

BackendOutcome.String returns the lowercase wire form used in the JSON encoder and the UI. Defined here so the JSON file owns the wire format.

type Binding

type Binding struct {
	Store       *ProfileStore
	ModuleID    string
	DeepEnabled bool // true when the configured mode is ProfileModeDeep
}

Binding is the per-instance pointer back to the parent Fastlike's profile store, captured at instance construction time. nil means profiling is disabled for that instance.

type CompileConfig

type CompileConfig struct {
	Mode ProfileMode
}

CompileConfig is the compile-time profile configuration consumed by Instance.compile. Carried from FastlikeOptions into Fastlike, then passed down to compile() before NewModule is called. Engine-level wiring lives in step 6; step 1 only plumbs the struct so the surface is stable.

type DeepMetrics

type DeepMetrics struct {
	BodyReadBytes  int64
	BodyWriteBytes int64

	CacheLookups int
	CacheInserts int
	CacheHits    int
	CacheMisses  int
	CacheStale   int

	// RequestHeaders / ResponseHeaders are populated once per request
	// by beginTrace / finalizeTrace: a snapshot of the downstream
	// request headers at the moment fastlike accepted the request, and
	// a snapshot of the downstream response headers at the moment the
	// guest's final response has flushed. Header names appear
	// canonical-cased; names on the redact list (Cookie, Set-Cookie,
	// Authorization, etc.) appear as "<redacted>" with their byte
	// counts still aggregated. No header values ever reach this slice.
	RequestHeaders  []HeaderSummary
	ResponseHeaders []HeaderSummary

	// StoreAccess is the sorted list rebuilt at finalize. Sort order:
	// Kind ascending, then Name ascending. Empty slice when no access
	// happened — the encoder still omits the field via omitempty.
	StoreAccess []StoreAccess

	// HeapSamples is the wasm linear memory size curve over the
	// request, captured at request start, finalize, and after each
	// hostcall boundary. Consecutive samples whose MemoryBytes match
	// the previous entry are dropped at recording time (wasm memory
	// grows monotonically, so once it stabilises further samples add
	// no information).
	//
	// IMPORTANT: this is wasm guest linear memory bytes, NOT the Go
	// host's runtime heap. A reader looking at the curve to debug a
	// guest-side leak should treat each sample as the size of the
	// `memory` export at that moment.
	HeapSamples        []HeapSample
	HeapSamplesDropped int
	// contains filtered or unexported fields
}

DeepMetrics carries fields populated only when the Fastlike was built with ProfileModeDeep. The pointer is nil on every other mode; encoders and the UI must branch on the pointer before reading any of the fields below. Adding a deep-only counter is two changes: a field here, and one bump call inside an `if i.trace != nil && i.trace.Deep != nil` block at the relevant xqd_* site.

Privacy contract: NOTHING in this struct may include key material, header values, body bytes, or secret values. Names and counts only. The plan's Privacy section is the source of truth and the profile_deep_privacy_test.go fixture pins the contract.

func NewDeepMetrics

func NewDeepMetrics() *DeepMetrics

NewDeepMetrics allocates the per-trace deep accumulator. Caller is responsible for placing the result on RequestTrace.Deep only when the configured mode is ProfileModeDeep.

func (*DeepMetrics) AddHeapSample

func (d *DeepMetrics) AddHeapSample(relativeNanos, memoryBytes int64)

AddHeapSample records one wasm linear memory observation. Samples that repeat the previously recorded size are dropped (wasm memory grows monotonically and usually stabilises, so hostcall boundaries would otherwise record the same value many times). Once the slab reaches DefaultHeapSampleCap the sample is dropped and HeapSamplesDropped is incremented instead.

func (*DeepMetrics) BumpStoreAccess

func (d *DeepMetrics) BumpStoreAccess(kind, name string)

BumpStoreAccess records one access to the named store of the given kind. Safe to call on a nil receiver; the no-op path means instrumentation sites can omit the nil check and the compiler inlines it away on hot paths when the receiver is statically known.

func (*DeepMetrics) Finalize

func (d *DeepMetrics) Finalize()

finalize flattens the accumulator map into StoreAccess. Called once per trace from Instance.finalizeTrace before CompleteTrace hands the trace to the store. After finalize the storeAccess map is cleared so the trace does not retain it.

func (*DeepMetrics) StoreAccessCount

func (d *DeepMetrics) StoreAccessCount(kind, name string) int

StoreAccessCount returns the number of recorded accesses against the named store before Finalize flattens the accumulator. Intended for tests and introspection; returns zero for an unknown (kind, name).

type HeaderSummary

type HeaderSummary struct {
	Name  string
	Count int
	Bytes int
}

HeaderSummary is the per-header deep-mode capture. Name is the canonical-case header name (http.CanonicalHeaderKey form) OR "<redacted>" when the name appears in the deny list below. Count is the number of distinct values the header had; Bytes is the total payload (sum of len(name) + len(value) across every value plus a constant approximation of the ": " + CRLF framing).

Privacy contract:

  • The Name field is either an http canonical header name or the literal string "<redacted>". No header values reach this struct.
  • When a header name appears in the deny list its Name is collapsed to "<redacted>", but Count and Bytes are still recorded so the operator can see "there is a large redacted header" without learning which one. Multiple redacted headers in the same direction produce multiple rows, each with Name=="<redacted>".

func SummarizeHeaders

func SummarizeHeaders(h http.Header) []HeaderSummary

SummarizeHeaders walks h and returns a sorted []HeaderSummary, redacting names that appear in redactedHeaderNames. Returns nil for an empty / nil input so JSON omitempty can drop the field entirely. Sort order: by Name ascending; redacted rows sort together under "<redacted>" (which sorts before any canonical header name because '<' < 'A').

type HeapAggregates

type HeapAggregates struct {
	Min   int64
	Max   int64
	Final int64
}

HeapAggregates summarises a HeapSamples slice into three scalar values: Min and Max wasm memory size observed during the request, and the Final value (the last recorded sample). Returns the zero struct when samples is empty so encoders can branch on len.

func HeapAggregatesOf

func HeapAggregatesOf(samples []HeapSample) HeapAggregates

HeapAggregatesOf computes min / max / final across the given heap sample slice. Encoders that surface only aggregates (Chrome, Firefox) call this; the native JSON keeps the full slice.

type HeapSample

type HeapSample struct {
	RelativeNanos int64
	MemoryBytes   int64
}

HeapSample is one wasm linear memory observation. Both fields are measured against absolute baselines: RelativeNanos from the parent trace's WallStart, MemoryBytes from wasm linear memory size at the observation point.

type NativeSample

type NativeSample struct {
	RelativeNanos int64
	Function      string
}

NativeSample is one sampled wasm function as captured by an external profiler (perf, samply) and joined back to the trace by PID + time window. The sample's RelativeNanos is measured from the parent trace's WallStart, so the same X axis the canvas viewer uses for hostcall and backend tracks applies directly. Function is the resolved wasm function name from the jitdump symbols (resolved by the external profiler before we see it); when the profiler couldn't resolve the frame, the field falls back to the raw hex address.

Samples are advisory: any number can attach to a trace, including zero. The JSON encoder omits the entire native_samples key when the slice is empty so non-native runs do not pollute the schema.

type NativeSampleEvent

type NativeSampleEvent struct {
	PID       int
	UnixNanos int64
	Function  string
}

NativeSampleEvent is one raw sample as produced by a native profiler (perf, samply) before it has been attached to any RequestTrace. The absolute UnixNanos timestamp lets the merge step join the sample to a trace by time window; the PID lets it filter out samples that belonged to other processes sharing the input stream.

This is intentionally a flat struct: parser stage produces it, merge stage consumes it, neither needs anything more. Multi-frame stacks would extend Function to a []string; for v1 the importer keeps only the top wasm frame, which is the field load-bearing for "what was the guest doing at this moment".

type NativeSampleImporter

type NativeSampleImporter interface {
	Import(io.Reader) ([]NativeSampleEvent, error)
}

NativeSampleImporter parses a profiler-tool output stream into NativeSampleEvent values. Implementations must return ErrNoNativeSamples when parsing succeeded but produced zero samples, so callers can branch on "nothing to merge" without conflating it with a parse failure. Any other error indicates the stream was malformed or unreadable.

type PerfScriptImporter

type PerfScriptImporter struct{}

PerfScriptImporter parses the text output of `perf script`. Compatible with the default per-sample layout `perf script` emits when fed a perf.data captured under `perf record -k CLOCK_REALTIME`. The CLOCK_REALTIME requirement is what makes timestamps comparable with the trace recorder's `time.Now()`-based WallStart; without it, MergeNativeSamples will reject every sample as "outside the time window" because perf's default CLOCK_MONOTONIC starts at boot. The CLI documents this in the helptext for -profile-native-samples (lands in a follow-up; today the importer is callable from embedder code or tests).

Only the top stack frame is retained per sample for now. Multi-frame stack capture is additive and lands when the viewer learns to render flamegraphs; right now there's nothing to display deeper frames in.

func NewPerfScriptImporter

func NewPerfScriptImporter() *PerfScriptImporter

NewPerfScriptImporter returns a ready-to-use importer. No configuration is needed; the parser is stateless.

func (*PerfScriptImporter) Import

Import reads `perf script` text from r, returns the parsed events, or ErrNoNativeSamples when parsing succeeded but no samples were found. Other errors indicate a malformed stream or I/O failure.

type ProfileMode

type ProfileMode string

ProfileMode selects the breadth of profiling instrumentation enabled for a Fastlike value. Modes are additive: combined implies native implies trace; deep implies combined.

const (
	ProfileModeOff      ProfileMode = "off"
	ProfileModeTrace    ProfileMode = "trace"
	ProfileModeNative   ProfileMode = "native"
	ProfileModeCombined ProfileMode = "combined"
	ProfileModeDeep     ProfileMode = "deep"
)

func (ProfileMode) IncludesTrace

func (m ProfileMode) IncludesTrace() bool

IncludesTrace reports whether the mode includes the always-on hostcall trace.

type ProfileStore

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

ProfileStore owns the per-Fastlike retention, in-flight set, and configuration for the viewer. A nil *ProfileStore means profiling is disabled.

func NewProfileStore

func NewProfileStore() *ProfileStore

NewProfileStore returns a store with default retention, async grace, and backend cap. FastlikeOption constructors mutate these defaults.

func (*ProfileStore) AsyncGrace

func (s *ProfileStore) AsyncGrace() time.Duration

AsyncGrace returns the configured grace period for in-flight async backends.

func (*ProfileStore) BackendCap

func (s *ProfileStore) BackendCap() int

BackendCap returns the configured per-request backend-call cap.

func (*ProfileStore) CompleteTrace

func (s *ProfileStore) CompleteTrace(t *RequestTrace)

CompleteTrace moves a trace from in-flight to completed, evicting the oldest entry when retention is exceeded.

func (*ProfileStore) Dir

func (s *ProfileStore) Dir() string

Dir returns the configured archive directory, or empty if disk archival is off.

func (*ProfileStore) Get

func (s *ProfileStore) Get(reqID uint64) *RequestTrace

Get returns the completed trace with the given request id, or nil.

func (*ProfileStore) InFlight

func (s *ProfileStore) InFlight() []*RequestTrace

InFlight returns a snapshot of currently-running traces.

func (*ProfileStore) NewRequestTrace

func (s *ProfileStore) NewRequestTrace(moduleID string, r *http.Request) *RequestTrace

NewRequestTrace allocates a trace, assigns a request id, and registers it as in-flight. The caller owns the returned pointer until CompleteTrace hands it back to the store.

func (*ProfileStore) Recent

func (s *ProfileStore) Recent(n int) []*RequestTrace

Recent returns up to n most recent completed traces, most recent first. Pass n <= 0 to fetch every retained trace.

func (*ProfileStore) Retain

func (s *ProfileStore) Retain() int

Retain returns the configured LRU size for completed traces.

func (*ProfileStore) SetAsyncGrace

func (s *ProfileStore) SetAsyncGrace(d time.Duration)

SetAsyncGrace sets the grace period for in-flight async backends. Negative values are clamped to zero, which disables the grace period.

func (*ProfileStore) SetBackendCap

func (s *ProfileStore) SetBackendCap(n int)

SetBackendCap sets the per-request backend-call cap. Values <= 0 are clamped to the default.

func (*ProfileStore) SetDir

func (s *ProfileStore) SetDir(dir string)

SetDir enables disk archival of completed traces under dir.

func (*ProfileStore) SetRetain

func (s *ProfileStore) SetRetain(n int)

SetRetain sets the LRU size for completed traces. Values <= 0 are clamped to the default.

func (*ProfileStore) SetUIAddr

func (s *ProfileStore) SetUIAddr(addr string)

SetUIAddr sets the UI bind address. Empty disables the listener.

func (*ProfileStore) SetUIAuthToken

func (s *ProfileStore) SetUIAuthToken(token string)

SetUIAuthToken sets the bearer token enforced on the UI endpoints.

func (*ProfileStore) SetUIInsecure

func (s *ProfileStore) SetUIInsecure(insecure bool)

SetUIInsecure allows a non-loopback UI bind without bearer auth.

func (*ProfileStore) UIAddr

func (s *ProfileStore) UIAddr() string

UIAddr returns the configured UI bind address. Empty means no listener.

func (*ProfileStore) UIAuthToken

func (s *ProfileStore) UIAuthToken() string

UIAuthToken returns the configured bearer token, or empty if unset.

func (*ProfileStore) UIInsecure

func (s *ProfileStore) UIInsecure() bool

UIInsecure reports whether the non-loopback-without-auth escape hatch is on.

type ProfileUI

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

ProfileUI is the read-only HTTP handler the operator points a browser at to inspect captured traces. It serves three routes:

  • GET / index of recent traces (newest first)
  • GET /r/{req_id} per-request page with summary, span table, and a server-rendered backend waterfall
  • GET /r/{req_id}.json the raw native JSON trace

The handler is intentionally allocation-light and template-driven so it can render without any client-side JavaScript. Live-tail (SSE) and the flame/waterfall canvas land in a later step. Auth and listener binding are the caller's responsibility (the CLI gates loopback vs non-loopback per the Security section of the plan); embedders are free to mount this handler behind their own middleware.

func NewProfileUI

func NewProfileUI(store *ProfileStore) *ProfileUI

NewProfileUI returns a handler bound to store. The store may be nil, in which case every route returns 503; this lets the CLI build the handler unconditionally and rely on the listener gate to suppress exposure when profiling is off.

func (*ProfileUI) ServeHTTP

func (p *ProfileUI) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

type RequestTrace

type RequestTrace struct {
	ReqID               uint64
	ModuleID            string
	Method              string
	URL                 string
	Status              int
	WallStart           time.Time
	WallNanos           int64
	HeaderFlushNanos    *int64
	GuestActiveNanos    int64
	HostcallNanos       int64
	NativeCPUNanos      *int64
	HijackedNanos       *int64
	Spans               []Span
	BackendCalls        []BackendCall
	NativeSamples       []NativeSample
	Dropped             int
	DroppedBackendCalls int
	Outcome             TraceOutcome
	Notes               []string

	// Deep is the deep-mode metrics struct, populated only when the
	// parent Fastlike was built with ProfileModeDeep. nil on every
	// other mode. Encoders and the UI MUST branch on this pointer
	// before reading any DeepMetrics field.
	Deep *DeepMetrics
	// contains filtered or unexported fields
}

RequestTrace is the per-request capture produced by the profile recorder. All time fields are nanoseconds; absolute times are nanos since WallStart.

func (*RequestTrace) MarshalJSON

func (t *RequestTrace) MarshalJSON() ([]byte, error)

MarshalJSON encodes a RequestTrace into the native wire schema. Internal representations (uint16 hostcall name indices, fixed-size tag slots, enum outcomes) are resolved to their stable wire form so consumers do not have to know about the in-memory layout. The schema is the contract every encoder (Firefox Gecko, Chrome Tracing, pprof) and the UI build on; changes here are breaking and need to land alongside golden-file updates.

func (*RequestTrace) SnapshotNativeSamples

func (t *RequestTrace) SnapshotNativeSamples() []NativeSample

SnapshotNativeSamples returns a defensive copy of t.NativeSamples captured under the per-trace RWMutex. Encoders and the UI use this rather than ranging t.NativeSamples directly so a concurrent MergeNativeSamples cannot race the iteration.

type ResponseObserver

type ResponseObserver interface {
	http.ResponseWriter
	Status() int
	BytesWritten() int64
	HeaderFlushed() *int64
	Hijacked() *int64
}

ResponseObserver is the subset of TraceResponseWriter that the rest of the runtime depends on: an http.ResponseWriter plus the four observation accessors the recorder reads at end-of-request.

func NewTraceResponseWriter

func NewTraceResponseWriter(w http.ResponseWriter) ResponseObserver

NewTraceResponseWriter wraps w so observations land on the returned ResponseObserver while preserving every optional interface w satisfies. The returned value is also an http.ResponseWriter so callers can use it directly in place of w.

type Span

type Span struct {
	NameIdx   uint16
	Start     int64
	Duration  int64
	RC        int32
	TagSlots  [4]int64
	TagStrIdx [2]uint16
}

Span is one hostcall invocation. NameIdx indexes hostcallNames.

type StoreAccess

type StoreAccess struct {
	Kind  string
	Name  string
	Count int
}

StoreAccess is one row of the deep-mode store access counter. Kind is "kv" / "config" / "secret" / "dictionary"; Name is the operator- supplied store identifier (already configuration the user wrote down and visible in fastlike's logs); Count is the number of accesses during this request.

type TraceOutcome

type TraceOutcome uint8

TraceOutcome categorises how a request exited.

const (
	TraceOutcomeNormal TraceOutcome = iota
	TraceOutcomeTrap
	TraceOutcomePanic
	TraceOutcomeLoopFail
	TraceOutcomeCtxCanceled
)

func (TraceOutcome) String

func (o TraceOutcome) String() string

String returns the lowercase wire form used in JSON encoders and the UI.

type TraceResponseWriter

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

TraceResponseWriter wraps an http.ResponseWriter so the profile recorder can observe the status code, response byte count, header-flush moment, and hijack moment without changing the guest-visible behavior of the response writer. It is constructed by NewTraceResponseWriter, which returns one of 16 pre-built wrapper types whose method set exactly matches the interfaces the underlying writer satisfied. This is the same approach httpsnoop takes; inlined here to avoid a dependency on a third-party package for one file.

The wrapper is safe for concurrent observation reads (Status, BytesWritten, HeaderFlushed, Hijacked) by code outside the request goroutine; the recording side runs only from the request goroutine.

func (*TraceResponseWriter) BytesWritten

func (t *TraceResponseWriter) BytesWritten() int64

func (*TraceResponseWriter) Header

func (t *TraceResponseWriter) Header() http.Header

func (*TraceResponseWriter) HeaderFlushed

func (t *TraceResponseWriter) HeaderFlushed() *int64

func (*TraceResponseWriter) Hijacked

func (t *TraceResponseWriter) Hijacked() *int64

func (*TraceResponseWriter) Status

func (t *TraceResponseWriter) Status() int

func (*TraceResponseWriter) Write

func (t *TraceResponseWriter) Write(b []byte) (int, error)

func (*TraceResponseWriter) WriteHeader

func (t *TraceResponseWriter) WriteHeader(status int)

Jump to

Keyboard shortcuts

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