Documentation
¶
Index ¶
- Constants
- Variables
- func EncodeChromeTrace(t *RequestTrace) ([]byte, error)
- func EncodeFirefoxGecko(t *RequestTrace) ([]byte, error)
- func EncodePprof(t *RequestTrace) ([]byte, error)
- func HostcallNameIndex(name string) uint16
- func IsLoopbackBindAddress(addr string) bool
- func MergeNativeSamples(store *ProfileStore, events []NativeSampleEvent, expectedPID int, ...) int
- func ModuleIDOf(wasmbytes []byte) string
- func NativeProfilerStrategy(mode ProfileMode) (wasmtime.ProfilingStrategy, bool)
- func NativeSamplingRequested(mode ProfileMode) bool
- func ResolveHostcallName(idx uint16) string
- func ValidateProfileUIAuth(addr, token string, insecure bool) error
- func WrapProfileUIAuth(h http.Handler, token string) http.Handler
- func WriteWasmSymbolSidecar(wasmbytes []byte, dir, moduleID string, mode ProfileMode) (string, error)
- type BackendCall
- type BackendOutcome
- type Binding
- type CompileConfig
- type DeepMetrics
- type HeaderSummary
- type HeapAggregates
- type HeapSample
- type NativeSample
- type NativeSampleEvent
- type NativeSampleImporter
- type PerfScriptImporter
- type ProfileMode
- type ProfileStore
- func (s *ProfileStore) AsyncGrace() time.Duration
- func (s *ProfileStore) BackendCap() int
- func (s *ProfileStore) CompleteTrace(t *RequestTrace)
- func (s *ProfileStore) Dir() string
- func (s *ProfileStore) Get(reqID uint64) *RequestTrace
- func (s *ProfileStore) InFlight() []*RequestTrace
- func (s *ProfileStore) NewRequestTrace(moduleID string, r *http.Request) *RequestTrace
- func (s *ProfileStore) Recent(n int) []*RequestTrace
- func (s *ProfileStore) Retain() int
- func (s *ProfileStore) SetAsyncGrace(d time.Duration)
- func (s *ProfileStore) SetBackendCap(n int)
- func (s *ProfileStore) SetDir(dir string)
- func (s *ProfileStore) SetRetain(n int)
- func (s *ProfileStore) SetUIAddr(addr string)
- func (s *ProfileStore) SetUIAuthToken(token string)
- func (s *ProfileStore) SetUIInsecure(insecure bool)
- func (s *ProfileStore) UIAddr() string
- func (s *ProfileStore) UIAuthToken() string
- func (s *ProfileStore) UIInsecure() bool
- type ProfileUI
- type RequestTrace
- type ResponseObserver
- type Span
- type StoreAccess
- type TraceOutcome
- type TraceResponseWriter
- func (t *TraceResponseWriter) BytesWritten() int64
- func (t *TraceResponseWriter) Header() http.Header
- func (t *TraceResponseWriter) HeaderFlushed() *int64
- func (t *TraceResponseWriter) Hijacked() *int64
- func (t *TraceResponseWriter) Status() int
- func (t *TraceResponseWriter) Write(b []byte) (int, error)
- func (t *TraceResponseWriter) WriteHeader(status int)
Constants ¶
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.
const (
DefaultProfileSpanCap = 8000
)
Variables ¶
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.
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 IsLoopbackBindAddress ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
func (p *PerfScriptImporter) Import(r io.Reader) ([]NativeSampleEvent, error)
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.
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 ¶
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) WriteHeader ¶
func (t *TraceResponseWriter) WriteHeader(status int)