wcprof

package
v1.0.0-beta.23 Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

wcprof: engine wall-clock profiling

wcprof is an experimental, cheap wall-clock profiler for the Dagger engine, built to find latency bottlenecks that CPU/memory profiles can't see (e.g. a 300ms serial tax before every container start). It is intentionally separate from OTel: events are fixed-size structs appended to sharded in-memory buffers with interned strings, and all heavy analysis happens offline.

What it records

Four event types, written by hooks at the engine's blocking choke points:

  • ops: timed intervals with a kind (call, call_exec, lazy, exec, exec_phase, service_start, session_phase, ...), a class (e.g. Container.withExec, exec.setupNetwork), an instance ident (recipe digest, exec ID), a structural parent (from context), and an outcome (hit/executed/joined/...).
  • waits: exact blocked-on intervals — who waited, on which op (or named resource, e.g. a cache-volume lock), why, from when to when. These are recorded at the choke points themselves (dagql singleflight, lazy-eval waiters, service starts, exec completion), not inferred from span nesting.
  • links: non-blocking correlations, most importantly "exec op X hosts nested client Y" so module-function calls back into the API are stitched under the exec that made them (wcprof-report puts them under the exec's exec.processRun, or its exec.workload when the run is split).
  • spans: the OTel span an op owns, when telemetry is on and the engine emits a span for the same work: a call with its own span, call_exec, dagql.publishResult, lazy, exec, its exec.containerStart, exec.runtimeStart and exec.processRun phases, and service_start. Dumped as {"e":"span","id":<op>,"tr":<trace ID>,"sp":<span ID>} in hex, so offline analysis can join an op to its span's attributes exactly.

Current hook points:

  • dagql/cache.go getOrInitCall: one call op per caller (cache hits and do-not-cache calls included — this deliberately records what OTel suppresses), one shared call_exec op per actual resolver execution, wait edges from every caller to it, and a dagql.publishResult op for publication cost.
  • dagql/cache.go evaluateOne: one lazy op per lazy-evaluation run, classed by the call that created the lazy value, with wait edges from the triggering and joining ops.
  • engine/engineutil/executor.go: one exec op per container run, one exec_phase op per setup phase (exec.setupNetwork, exec.setupRootfs, ..., exec.runContainer), plus a split of the container run into exec.containerStart (engine overhead up to launching runc), exec.runtimeStart (runc creating the container, up to the pid file it writes once the workload is released) and exec.processRun (user work). When the container runs under the injected /.init, /.init reports its own timing on a pipe and exec.processRun is split further into exec.initStart (exec of /.init up to its main), exec.processSpawn (its setup and the fork/exec of the command), exec.workload (the command, user work) and exec.processExit (/.init exiting, runc reaping it and exiting, and the stdio drain). The exec op is linked to every nested client the container connects.
  • core/lazy_state.go, dagql/cache.go syncResultSnapshotLeases: lock waits on a lazy group's body (core.lazyGroupOnce.mu), the whole-op latch (core.LazyState.LazyMu) and a result's snapshot lease sync (dagql.sharedResult.leaseSyncMu), recorded only when the lock is contended (wcprof.Lock).
  • core/container_exec.go: cache-volume lock waits, mount preparation and output-commit phases, and the wait on the executor.
  • core/services.go: service_start ops with singleflight wait edges.
  • engine/server/session.go serveQuery: per-query session_phase ops (attachables wait, workspace load, module load, schema build, query).

Enabling and dumping

Two scopes:

  • Per-session: dagger --profile <anything> (hidden flag) sets ClientMetadata.Profile. Only that session's work is recorded — including its nested module/SDK clients — via a context mark applied by the session server and propagated to derived contexts. Caveat: if a profiled call joins in-flight identical work spawned by an unprofiled session, the join shows up as an unresolved wait (modeled by the analyzer as a fixed delay) since the other session's execution wasn't recorded.

  • Engine-global: record everything. Enable at startup with the engine's --wcprof flag (hidden), or at runtime by POSTing to the debug endpoint:

    curl -X POST -d on  http://<engine-debug-addr>/debug/wcprof/enabled
    curl -X POST -d off http://<engine-debug-addr>/debug/wcprof/enabled
    curl http://<engine-debug-addr>/debug/wcprof/enabled   # report state
    

    POSTing off stops engine-global recording but keeps buffered events dumpable, lets --profile sessions keep recording, and lets in-flight recorded ops finish; on re-enables on the same recorder (dumps from before and after merge cleanly since the epoch is unchanged).

The recorder keeps up to ~4M events (about 300 MB); events past that are dropped and counted in the dump header's dropped_events.

  • Dump: GET http://<engine-debug-addr>/debug/wcprof/dump streams a JSON header line (string tables, open ops) followed by one JSON event per line, and flushes the buffer (pass ?flush=0 to keep it).
  • Dump at stop: with engine-global recording on, a stopping engine writes the whole buffer, unflushed, to <engine root>/wcprof/stop.ndjson.gz (wcprof.StopDumpPath): on the stop signal (engine.stop.signal), when each stop phase starts and ends (engine.stop.<phase> ops under engine.stop, waiting for a running garbage collection included), when the server's stop ends, and when each telemetry drain after it starts and ends (engine.exit.<step>). Each write replaces the file atomically and is itself a wcprof.stopDump op, so an engine killed mid-stop leaves the last complete dump, with the phase it was in as an open op. Writes run in the background, one at a time, so the stop doesn't wait for them (a large recording's dump takes seconds to write): requests made during a write become one more write, from the state at its start. The dump on disk lags the stop by at most the write in progress and one more; as it exits, the engine waits for one last write, so a stop that finishes leaves its whole record. The write that produced a dump is open in it too, as a wcprof.stopDump op, since it snapshots the recorder before it ends. So after a fully drained stop, the dump an engine leaves has that write as its only open op; a phase still running when the stop gave up on it (its deadline passed) stays open beside it. In a dump taken mid-stop, the work running at the snapshot is open as well: the stop and its current phase. An open op alone doesn't prove the work was cut off.

Typical dev-engine workflow:

./hack/dev   # build + start dagger-engine.dev (publishes debug port 6060)
./hack/with-dev ./bin/dagger --profile call engine-dev container sync
curl -s http://localhost:6060/debug/wcprof/dump > /tmp/wcprof.dump

Dumps are NDJSON: a DumpHeader line (with the interned strings table that class/ident/client indexes point into), then one DumpEvent per line. Read them with wcprof.ReadDump.

Analysis

This package is only the recorder; analysis tooling lives outside this repo. The offline what-if analyzer (cmd/wcprof-analyze, engine/wcprof/wcanalyze: replay-based what-if rankings, blocking chains, dead air) was moved out in #13588.

For agent-driven debugging, the engine-lab dev module (.dagger/modules/engine-lab) has wcprofEnable, wcprofCapture and wcprofReport tools: capture a dump from a from-source engine, then report per-class self time, per-parent child breakdowns, per-client activity, op subtrees, or name-resolved events through jq. Its wcprof-report helper is built against this package at tool-call time, so it tracks the dump format. See the engine-lab skill for the workflow.

Status / caveats

This is a prototype for validating the approach:

  • there is one engine-wide recorder and event buffer; per-session enablement scopes what gets recorded, but all enabled sessions share the buffer and dumps (the analyzer can group by client/root, but the dump endpoint has no per-session filter).
  • events are kept until dumped; long sessions can hit the event cap (the dump and the analyzer both surface the drop count loudly).
  • leaf I/O ops (git fetch, image pull, filesync) are not yet instrumented; they currently show up as self-time of their calling op, and the "dead air" / unexplained-self-time reports are the guide for where to add hooks next.

Documentation

Overview

Package wcprof implements cheap wall-clock profiling for the engine.

It records operation intervals, wait (blocked-on) intervals, and link events into an in-memory buffer, for offline analysis of where wall-clock time goes and which operation classes are bottlenecks. It is intentionally not OTel: events are fixed-size structs appended to sharded in-memory buffers, with all strings interned. The expensive work (graph reconstruction, counterfactual simulation) happens offline after dumping the buffer via the engine debug endpoint.

Recording can be enabled two ways:

  • Engine-global: the engine's --wcprof flag, or POSTing "on" to the /debug/wcprof/enabled endpoint. All work on the engine is recorded until "off" is POSTed to the same endpoint.
  • Per-session: a client connecting with ClientMetadata.Profile set (the hidden --profile CLI flag). Only work attributable to that session (including its nested module/SDK clients) is recorded: the session server marks the session's contexts via ContextWithProfiling, and the mark propagates to derived contexts.

When profiling has never been enabled, all recording calls are a single atomic load + nil check.

Index

Constants

View Source
const (
	DumpTypeResource    = "resource"
	DumpTypeAcquisition = "acquisition"
)

Dump event types for reported resources and acquisitions.

View Source
const (
	// UsageScopeExecCgroup is the exec's resource cgroup: the workload and the
	// mount helpers that run beside it, read after mount teardown.
	UsageScopeExecCgroup = "exec_cgroup"
	// UsageScopeMountHelpers is the mount helpers' own cgroup inside the
	// exec's resource cgroup, read with it, and only when a helper ran: the
	// workload's usage is the exec cgroup's less this.
	UsageScopeMountHelpers = "mount_helpers"
)

Usage scopes of an exec.

View Source
const DumpSchemaVersion = 1

DumpSchemaVersion identifies the dump wire format.

View Source
const DumpTypeUsage = "usage"

DumpTypeUsage is the DumpEvent.Type of a resource reading (RecordUsage).

Variables

This section is empty.

Functions

func ContextWithOpID

func ContextWithOpID(ctx context.Context, opID uint64) context.Context

ContextWithOpID returns a context carrying the given op ID as the current op. Useful when handing work to a goroutine that should be attributed to an existing op.

func ContextWithProfiling

func ContextWithProfiling(ctx context.Context) context.Context

ContextWithProfiling marks ctx (and any context derived from it) for recording even when engine-global recording is off. The session server applies this to contexts of sessions that opted into profiling.

func CurrentOpID

func CurrentOpID(ctx context.Context) uint64

CurrentOpID returns the profiling op ID carried by ctx, or 0.

func DisableGlobal

func DisableGlobal()

DisableGlobal turns off engine-global recording. The recorder and its buffered events are retained for dumping, sessions that explicitly enabled profiling keep recording, and already-recording flows (contexts carrying a recorded op) run to completion.

func EnableGlobal

func EnableGlobal()

EnableGlobal turns on recording of all work on the engine.

func Enabled

func Enabled(ctx context.Context) bool

Enabled reports whether work running under ctx should be recorded. Call sites use it to skip computing op metadata (class/ident strings) when profiling is off. When profiling has never been enabled this is a single atomic load + nil check.

func EnsureRecorder

func EnsureRecorder()

EnsureRecorder creates the recorder if it does not exist yet, without turning on engine-global recording. Used when a session opts into profiling.

func FlushStopDump

func FlushStopDump(path string) error

FlushStopDump asks for the stop dump at path to be rewritten and waits for a write that began after the call, returning its error: the engine's last state before it exits is then on disk.

func GloballyEnabled

func GloballyEnabled() bool

GloballyEnabled reports whether engine-global recording is on.

func Link(ctx context.Context, kind LinkKind, fromOpID, targetOpID uint64, ident string, resultID uint64)

Link records a non-blocking correlation from fromOpID (0 means the current op in ctx) to an op, an interned ident, and/or a dagql result ID.

func LinkOTelParent

func LinkOTelParent(ctx context.Context, opID uint64, traceID, spanID string)

LinkOTelParent records that the engine op opID was caused by the span (traceID, spanID), e.g. a request a reported task made: in the dump the link resolves to that span's reported op, if it was recorded. A flushing dump drops the links it couldn't resolve, as most callers aren't reported spans: take it after the clients have exported their spans.

func Lock

func Lock(ctx context.Context, mu *sync.Mutex, ident string)

Lock acquires mu. When mu is held by someone else and work under ctx is being recorded, the time spent blocked is recorded as a lock wait of the current op on the resource named ident; otherwise it is mu.Lock with a TryLock in front. Use it for engine-internal mutexes held across slow work, whose waits would otherwise show up as the waiter's own time.

func MapOTelSpan

func MapOTelSpan(ctx context.Context, opID uint64, traceID, spanID string)

MapOTelSpan records that the engine op opID was begun under the span (traceID, spanID), e.g. a dagql call under its call span: a reported op whose causal parent is that span (a client inside the call's exec, or the module the call runs) resolves to opID in the dump. The first op mapped to a span keeps it, since calls under a telemetry-suppressed caller share the caller's span and the outermost of them is the one it stands for.

func NowNS

func NowNS() int64

NowNS returns the current recorder-relative timestamp, or 0 when profiling is disabled.

func ReadDump

func ReadDump(rd io.Reader) (*DumpHeader, []DumpEvent, error)

ReadDump parses a dump produced by WriteDump.

func ReadLock

func ReadLock(ctx context.Context, mu *sync.RWMutex, ident string)

ReadLock acquires mu for reading, recording a blocked acquisition as Lock does.

func RecordOp

func RecordOp(ctx context.Context, kind OpKind, class string, opts OpOpts, startNS, endNS int64, outcome Outcome) uint64

RecordOp records an already-completed op interval with explicit timestamps (from NowNS). Useful when an op's boundaries are observed from callbacks where holding an *Op handle would race. Returns the op ID, or 0 when profiling is disabled.

func RecordProducerComplete

func RecordProducerComplete(ctx context.Context, producer string, spanCount, dropped int64)

RecordProducerComplete records a producer's declaration of how many spans it reported and how many it failed to export.

func RecordReported

func RecordReported(ctx context.Context, op ReportedOp)

RecordReported records one reported op. A span already recorded is not recorded again (live and final exports of one span), and only ended spans are recorded. A no-op when profiling is disabled for ctx.

func RecordSpan

func RecordSpan(ctx context.Context, opID uint64, traceID [16]byte, spanID [8]byte)

RecordSpan is SetSpan for an op recorded by ID (RecordOp).

func RecordUsage

func RecordUsage(ctx context.Context, opID uint64, scope string, u Usage)

RecordUsage attaches a resource reading for scope to the op opID. It is a no-op when profiling is disabled for ctx or opID is 0. Readings count against the recorder's event cap like other events.

func RequestStopDump

func RequestStopDump(path string)

RequestStopDump asks for the engine's stop dump at path to be rewritten, while the engine records everything (GloballyEnabled), and returns without waiting for the write. Each write is an op of its own, wcprof.stopDump. The dump on disk lags the latest request by at most the write in progress and one more; FlushStopDump waits for it.

func SetActiveForTest

func SetActiveForTest(r *Recorder, on bool) func()

SetActiveForTest installs r as the recorder, recording globally when on, and returns a function restoring the previous state. For tests of code that records through the package-level API.

func StopDumpPath

func StopDumpPath(root string) string

StopDumpPath is where an engine with state directory root keeps the dump of its stop (see WriteDumpFile).

func WriteDumpFile

func WriteDumpFile(path string) (err error)

WriteDumpFile writes the recorder's dump to path, gzipped, without flushing the recorder. It writes a temporary file beside path and renames it over path, so a reader, or an engine killed mid-write, never leaves a partial dump. It does nothing when profiling was never enabled.

The engine writes its stop dump this way (RequestStopDump) when it receives SIGTERM, and as each graceful-stop phase and final drain begins and ends: an engine killed before it exits leaves the dump of its last finished write, with the work still running then as open ops. It compresses at gzip's fastest level, since it runs during the stop it records.

func WriteLock

func WriteLock(ctx context.Context, mu *sync.RWMutex, ident string)

WriteLock acquires mu for writing, recording a blocked acquisition as Lock does.

Types

type DumpEvent

type DumpEvent struct {
	Type     string `json:"e"`
	OpKind   string `json:"k,omitempty"`
	WorkType string `json:"w,omitempty"`
	Outcome  string `json:"o,omitempty"`
	Reason   string `json:"rs,omitempty"`
	LinkKind string `json:"lk,omitempty"`

	OpID     uint64 `json:"id,omitempty"`
	ParentID uint64 `json:"p,omitempty"`
	TargetID uint64 `json:"t,omitempty"`
	ResultID uint64 `json:"r,omitempty"`

	ClassID  uint32 `json:"c,omitempty"`
	IdentID  uint32 `json:"i,omitempty"`
	ClientID uint32 `json:"cl,omitempty"`
	MetaID   uint32 `json:"m,omitempty"`

	// TraceID and SpanID are a span event's OTel span, in lower-case hex.
	TraceID string `json:"tr,omitempty"`
	SpanID  string `json:"sp,omitempty"`

	StartNS int64 `json:"s"`
	EndNS   int64 `json:"d"`

	// Reported ops (see reported.go) also carry their label and producer.
	LabelID    uint32 `json:"lb,omitempty"`
	ProducerID uint32 `json:"pr,omitempty"`
	// Resource events ("e":"resource") describe the resource op OpID; IdentID
	// is its display name.
	Capacity int64  `json:"cap,omitempty"`
	PolicyID uint32 `json:"pol,omitempty"`
	// Acquisition events ("e":"acquisition") describe the acquire op OpID:
	// TargetID is its resource op, Priority its priority, MetaID its
	// priority inputs (a JSON object), GrantedNS when it was granted (0 if
	// never), Outcome released, cancelled or held.
	Priority  int64 `json:"pri,omitempty"`
	GrantedNS int64 `json:"gr,omitempty"`

	// Usage events ("e":"usage") carry a resource reading for the op OpID:
	// ClassID names the scope (see the UsageScope constants), StartNS and
	// EndNS are when it was read.
	CPUUserUS    int64 `json:"cu,omitempty"`
	CPUSystemUS  int64 `json:"cs,omitempty"`
	MemPeakBytes int64 `json:"mp,omitempty"`
}

DumpEvent is the JSON form of an Event. Short keys keep dumps compact.

type DumpHeader

type DumpHeader struct {
	SchemaVersion  int      `json:"schema_version"`
	EpochUnixNano  int64    `json:"epoch_unix_nano"`
	DumpedUnixNano int64    `json:"dumped_unix_nano"`
	DroppedEvents  uint64   `json:"dropped_events"`
	EventCount     int      `json:"event_count"`
	Strings        []string `json:"strings"`
	// OpenOps are ops begun but not ended at dump time (e.g. in-flight or
	// hung work).
	OpenOps []DumpOpenOp `json:"open_ops,omitempty"`
	// Producers are the clients that reported their own work: per producer,
	// the spans received and, once it declared them, the count it reported
	// and how many it failed to export. A producer whose Received is below
	// its Declared, or that never declared, makes the dump's reported work
	// incomplete.
	Producers []DumpProducer `json:"producers,omitempty"`
}

DumpHeader is the first line of a dump. The remaining lines are one DumpEvent JSON object per line.

type DumpOpenOp

type DumpOpenOp struct {
	OpID     uint64 `json:"op_id"`
	ParentID uint64 `json:"parent_id,omitempty"`
	Kind     string `json:"kind"`
	WorkType string `json:"work_type,omitempty"`
	ClassID  uint32 `json:"class_id,omitempty"`
	IdentID  uint32 `json:"ident_id,omitempty"`
	ClientID uint32 `json:"client_id,omitempty"`
	MetaID   uint32 `json:"meta_id,omitempty"`
	StartNS  int64  `json:"start_ns"`
}

DumpOpenOp describes an in-progress op at dump time.

type DumpProducer

type DumpProducer struct {
	Producer string `json:"producer"`
	Received int64  `json:"received"`
	Declared int64  `json:"declared,omitempty"`
	Dropped  int64  `json:"dropped,omitempty"`
	Complete bool   `json:"complete,omitempty"`
}

DumpProducer is one producer's completeness accounting.

type Event

type Event struct {
	Type     EventType
	OpKind   OpKind
	WorkType WorkType
	Outcome  Outcome
	Reason   WaitReason
	LinkKind LinkKind

	OpID     uint64
	ParentID uint64
	TargetID uint64
	ResultID uint64

	ClassID  uint32
	IdentID  uint32
	ClientID uint32
	// MetaID is an interned op-metadata string (currently the canonical
	// scalar JSON-array encoding of a user exec's scrubbed argv), 0 when none.
	MetaID uint32

	StartNS int64
	EndNS   int64
}

Event is one fixed-size profiling record. Field meaning varies by Type:

  • Op: OpID/ParentID identify the op and its structural parent. StartNS and EndNS bound the interval. ClassID/IdentID/ClientID are interned strings. ResultID is the dagql shared result ID when known.
  • Wait: ParentID is the waiting op, TargetID the awaited op (or 0 when waiting on a named resource, in which case IdentID names it).
  • Link: ParentID is the from-op, TargetID an op (optional), IdentID an interned identifier (optional), ResultID a dagql result (optional).
  • Span: OpID is the op, TargetID and ResultID the high and low halves of its span's trace ID, ParentID the span ID, all big-endian.

type EventType

type EventType uint8

EventType discriminates the union in Event.

const (
	EventTypeInvalid EventType = iota
	// EventTypeOp is a completed operation interval.
	EventTypeOp
	// EventTypeWait is a completed wait interval: an op was blocked on
	// another op (or named resource) from Start to End.
	EventTypeWait
	// EventTypeLink is a non-blocking correlation between an op and another
	// op or an interned identifier.
	EventTypeLink
	// EventTypeSpan names the OTel span an op owns: the span the engine
	// emits for the same work.
	EventTypeSpan
)

type LinkKind

type LinkKind uint8

LinkKind classifies link events.

const (
	LinkKindInvalid LinkKind = iota
	// LinkKindNestedClient: op (an exec) hosts a nested client whose ID is
	// the link's interned ident. Ops recorded for that client belong under
	// this exec.
	LinkKindNestedClient
	// LinkKindResult: op produced or returned the dagql result with
	// ResultID.
	LinkKindResult
	// LinkKindReusedResult: op was satisfied by reusing the dagql result
	// with ResultID (cache hit).
	LinkKindReusedResult
	// LinkKindReportedCause: the engine op (from) was caused by the reported
	// op (target), e.g. a request a reported task made.
	LinkKindReportedCause
	// LinkKindUnresolvedSpan: a reported op (from) refers to a span (the
	// ident, "<trace>/<span>") that wasn't recorded by the time of the dump.
	LinkKindUnresolvedSpan
)

func (LinkKind) String

func (k LinkKind) String() string

type Op

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

Op is a handle for an in-progress operation. A nil *Op is valid and all methods are no-ops, so call sites do not need to check whether profiling is enabled.

func BeginOp

func BeginOp(ctx context.Context, kind OpKind, class string, opts OpOpts) (context.Context, *Op)

BeginOp starts recording an operation. It returns a derived context that carries the new op as the current op (so nested ops and waits parent to it), and a handle to end it. When profiling is disabled it returns ctx unchanged and a nil handle.

func (*Op) End

func (op *Op) End(outcome Outcome)

End records the op's completion.

func (*Op) EndErr

func (op *Op) EndErr(err error)

EndErr records completion with OutcomeOK or OutcomeError based on err.

func (*Op) EndWithResult

func (op *Op) EndWithResult(outcome Outcome, resultID uint64)

EndWithResult records the op's completion, associating it with a dagql shared result ID when non-zero.

func (*Op) ID

func (op *Op) ID() uint64

ID returns the op's ID, or 0 for a nil handle.

func (*Op) OutcomeHint

func (op *Op) OutcomeHint() Outcome

OutcomeHint returns the hint set by SetOutcomeHint, or OutcomeNone.

func (*Op) SetIdent

func (op *Op) SetIdent(ident string)

SetIdent updates the op's instance identity (useful when it only becomes known after the op began, e.g. a recipe digest derived mid-call). Must be called from the goroutine that owns the op, before End.

func (*Op) SetOutcomeHint

func (op *Op) SetOutcomeHint(outcome Outcome)

outcomeHint carries an outcome decided mid-op (e.g. joined vs executed), read back by the code that ends the op.

func (*Op) SetSpan

func (op *Op) SetSpan(traceID [16]byte, spanID [8]byte)

SetSpan records the OTel span the op owns, the span the engine emits for the same work, so offline analysis can join the op to the span's attributes. A zero trace or span ID records nothing.

func (*Op) StartTime

func (op *Op) StartTime() time.Time

StartTime is when the op began, or the zero time for a nil handle.

type OpKind

type OpKind uint8

OpKind classifies operations.

const (
	OpKindInvalid OpKind = iota
	// OpKindCall is one dagql GetOrInitCall invocation (per caller,
	// including cache hits and singleflight joiners).
	OpKindCall
	// OpKindCallExec is the shared execution of a call's resolver function.
	// Singleflighted callers all wait on one of these.
	OpKindCallExec
	// OpKindLazy is one run of a lazy evaluation callback for a result.
	OpKindLazy
	// OpKindExecPhase is a setup/run phase of a container exec
	// (e.g. exec.setupNetwork, exec.runContainer).
	OpKindExecPhase
	// OpKindExec is the overall run of a container by the executor.
	OpKindExec
	// OpKindServiceStart is the start (incl. health check) of a service.
	OpKindServiceStart
	// OpKindSessionPhase is a per-query session serving phase
	// (e.g. session.workspaceLoad, session.query).
	OpKindSessionPhase
	// OpKindIO is a leaf I/O operation (git fetch, image pull, filesync...).
	OpKindIO
	// OpKindInternal is engine-internal background work (gc, persistence).
	OpKindInternal
	// OpKindTask is one unit of a client's own work, reported by the client
	// (e.g. one package of a Go build); see the reporting contract in
	// engine/wcprof/contract.
	OpKindTask
	// OpKindTaskGroup is a reported parent whose child tasks run concurrently,
	// ordered only by their dependency waits and resource acquisitions.
	OpKindTaskGroup
	// OpKindResource is a reported limited resource instance (capacity and
	// admission policy), for the life of the instance.
	OpKindResource
	// OpKindAcquire is a reported acquisition of one unit of a resource, from
	// the request to the release.
	OpKindAcquire
)

func ParseOpKind

func ParseOpKind(s string) OpKind

ParseOpKind is the OpKind named s (as OpKind.String writes it), or OpKindInvalid.

func (OpKind) String

func (k OpKind) String() string

type OpOpts

type OpOpts struct {
	// Ident is an instance identity for the op (e.g. recipe digest, exec ID).
	Ident string
	// ClientID is the dagger client this op serves, when known.
	ClientID string
	// ResultID may be set at Begin time when already known.
	WorkType WorkType
	// Argv is the user command for a container-exec op (already scrubbed and
	// bounded by the caller). The recorder marshals it to the canonical scalar
	// JSON-array string and interns it as the op's MetaID, so offline analysis
	// can rank the exec by its real command. Empty for non-exec ops.
	Argv []string
	// Start, when set, is when the op began, for an op whose work began
	// before BeginOp (e.g. at its OTel span's start); zero is now.
	Start time.Time
}

OpOpts carries optional per-op metadata.

type Outcome

type Outcome uint8

Outcome describes how an op completed.

const (
	OutcomeNone Outcome = iota
	// OutcomeHit: dagql call satisfied from cache.
	OutcomeHit
	// OutcomeExecuted: dagql call missed cache and this caller spawned the
	// execution.
	OutcomeExecuted
	// OutcomeJoined: dagql call missed cache and joined an in-flight
	// execution started by another caller.
	OutcomeJoined
	// OutcomeDoNotCache: dagql call executed inline without caching.
	OutcomeDoNotCache
	// OutcomeOK: generic success for non-call ops.
	OutcomeOK
	// OutcomeError: op failed.
	OutcomeError
	// OutcomeCanceled: op canceled.
	OutcomeCanceled
)

func (Outcome) String

func (o Outcome) String() string

type Recorder

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

Recorder collects profiling events. Safe for concurrent use.

func Active

func Active() *Recorder

Active returns the recorder, or nil if profiling was never enabled. The recorder outlives DisableGlobal so buffered events can still be dumped; use GloballyEnabled/Enabled to ask whether work is being recorded.

func NewRecorder

func NewRecorder(maxEvents int64) *Recorder

NewRecorder returns a recorder with the given event cap (<=0 means default).

func (*Recorder) Intern

func (r *Recorder) Intern(s string) uint32

Intern interns s and returns its table ID.

func (*Recorder) Now

func (r *Recorder) Now() int64

Now returns the current recorder-relative timestamp in nanoseconds.

func (*Recorder) WriteDump

func (r *Recorder) WriteDump(w io.Writer, flush bool) error

WriteDump streams the recorder state to w: one DumpHeader JSON line, then one DumpEvent JSON line per event. When flush is true, recorded events are removed from the buffer (the string table, open ops, and op ID counter are retained so later dumps remain consistent).

type ReportedAcquisition

type ReportedAcquisition struct {
	ResourceSpanID string
	Priority       int64
	PriorityInputs map[string]string
	// GrantedUnixNS is when the unit was granted, 0 if never.
	GrantedUnixNS int64
	Outcome       string
}

ReportedAcquisition describes an "acquire" op.

type ReportedOp

type ReportedOp struct {
	TraceID string
	SpanID  string
	// ParentSpanID is the op's causal parent span (same trace), "" for none.
	ParentSpanID string
	Kind         OpKind
	Work         WorkType
	// Name is the span's name, the op's class.
	Name        string
	Label       string
	Producer    string
	StartUnixNS int64
	EndUnixNS   int64
	Waits       []ReportedWait
	Resource    *ReportedResource
	Acquisition *ReportedAcquisition
}

ReportedOp is one reported span to record, already decoded.

type ReportedResource

type ReportedResource struct {
	// Name is the resource's display name, e.g. "gobuild.slots".
	Name     string
	Capacity int64
	Policy   string
}

ReportedResource describes a "resource" op.

type ReportedWait

type ReportedWait struct {
	TargetTraceID string
	TargetSpanID  string
	Reason        WaitReason
	Ident         string
	StartUnixNS   int64
	EndUnixNS     int64
}

ReportedWait is a wait link of a reported op.

type Usage

type Usage struct {
	CPUUserUS   int64
	CPUSystemUS int64
	// MemPeakBytes is the scope's peak memory, 0 when it couldn't be read.
	MemPeakBytes int64
}

Usage is a resource reading attached to an op: the CPU time and peak memory of one scope (see the UsageScope constants), read once the scope's work is done.

type Wait

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

Wait is a handle for an in-progress wait interval. A nil *Wait is valid and End is a no-op.

func BeginWait

func BeginWait(ctx context.Context, targetOpID uint64, reason WaitReason) *Wait

BeginWait starts recording that the current op (from ctx) is blocked on targetOpID for the given reason. Returns nil when profiling is disabled.

func BeginWaitIdent

func BeginWaitIdent(ctx context.Context, ident string, reason WaitReason) *Wait

BeginWaitIdent is BeginWait for waits on a named resource (e.g. a lock) rather than another op.

func (*Wait) End

func (w *Wait) End()

End records the wait interval.

func (*Wait) SetTarget

func (w *Wait) SetTarget(targetOpID uint64)

SetTarget updates the awaited op (useful when the target becomes known only after the wait began).

type WaitReason

type WaitReason uint8

WaitReason describes why an op was blocked.

const (
	WaitReasonInvalid WaitReason = iota
	// WaitReasonCallExec: blocked waiting for a call's resolver execution
	// (the caller that spawned it).
	WaitReasonCallExec
	// WaitReasonSingleflight: blocked joining another caller's in-flight
	// execution of the same call.
	WaitReasonSingleflight
	// WaitReasonLazy: blocked waiting for a lazy evaluation to finish.
	WaitReasonLazy
	// WaitReasonService: blocked waiting for a service to start/be healthy.
	WaitReasonService
	// WaitReasonLock: blocked acquiring a lock (target is an interned
	// resource name, not an op).
	WaitReasonLock
	// WaitReasonExec: blocked waiting for a container exec to finish.
	WaitReasonExec
	// WaitReasonIO: blocked on external I/O.
	WaitReasonIO
	// WaitReasonDependency: a reported task waited for one of its
	// dependencies' results (zero-length when it was already done).
	WaitReasonDependency
)

func ParseWaitReason

func ParseWaitReason(s string) WaitReason

ParseWaitReason is the WaitReason named s, or WaitReasonInvalid.

func (WaitReason) String

func (r WaitReason) String() string

type WorkType

type WorkType uint8

WorkType is a coarse attribution of an op's self-time, used by analysis to separate actionable engine overhead from user workload time and external I/O time.

const (
	WorkTypeEngine WorkType = iota
	// WorkTypeUser is time running user-controlled work (e.g. the container
	// process itself).
	WorkTypeUser
	// WorkTypeExternal is time bound on external systems (registry pulls,
	// git remotes, host filesync...).
	WorkTypeExternal
)

func ParseWorkType

func ParseWorkType(s string) WorkType

ParseWorkType is the WorkType named s, or WorkTypeEngine.

func (WorkType) String

func (w WorkType) String() string

Directories

Path Synopsis
Package contract decodes the wcprof reporting contract: how a client or a program inside an exec reports its own work (tasks and task groups), what each task waited for (dependency waits), its limited resources and the acquisitions of them, and its completeness, as OTel spans and links with the wcprof.* attributes of engine/telemetryattrs.
Package contract decodes the wcprof reporting contract: how a client or a program inside an exec reports its own work (tasks and task groups), what each task waited for (dependency waits), its limited resources and the acquisitions of them, and its completeness, as OTel spans and links with the wcprof.* attributes of engine/telemetryattrs.

Jump to

Keyboard shortcuts

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