mcpperf

package
v0.0.35 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package mcpperf serves this Go process's runtime performance data over a read-only Model Context Protocol (MCP) server, so an agent can introspect the harness's own latency, memory, goroutine, and profile state through MCP tools and resources instead of a human reading raw /metrics or /debug/pprof.

What it exposes

Every tool and resource returns a REDUCED, numeric summary — never a raw blob. Histograms become count + p50/p90/p99 bucket UPPER BOUNDS; profiles become a ranked top-N of function names; the runtime snapshot is a small JSON DTO. Raw artifacts (a full pprof CPU profile, a flight-recorder trace) are offered ONLY as user-audience resource_links pointing at the existing loopback admin endpoints (/debug/pprof/*, /debug/flightrecorder) — this server never holds an artifact store and never streams a multi-MB blob into the model's context.

Transport

Streamable HTTP only (a pure http.Handler), per the project's hard no-stdio constraint: no MCP server process is ever spawned and the stdio transport is never imported. Handler is built with the SDK's default options, which leaves the SDK's localhost / DNS-rebinding protection ON — a request that arrives on a loopback listener with a non-loopback Host header is rejected with 403. The surface is UNAUTHENTICATED by design: it is meant to be mounted on the same loopback admin listener as the rest of the perf surface, never exposed publicly. Its output can embed goroutine-derived function names and timing, so loopback-only is a security requirement (see docs/adr/0018-perf-observability.md).

Layering

This is an edge adapter constructed by dependency injection (the Deps struct): it imports the MCP SDK, the telemetry adapter (only for the RuntimeSnapshot DTO type), google/pprof for profile parsing, and the prometheus client model — and NOTHING from the domain, port, or agent layers, none of which may import it. It is not wired into any composition root by this commit; that is a later step.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Handler

func Handler(d Deps) http.Handler

Handler builds the Streamable HTTP handler for the perf MCP server. It uses the SDK default options (nil), which keeps the SDK's localhost / DNS-rebinding protection ON — NEVER set DisableLocalhostProtection. The returned handler is a pure http.Handler meant to be mounted on the loopback admin listener; it spawns no process and opens no outbound connection, satisfying the no-stdio constraint by construction.

The server is constructed once and shared across requests via the getServer closure: this server is stateless across calls (every tool reads live process state), so one instance is correct and avoids per-request allocation.

func NewServer

func NewServer(d Deps) *mcpsdk.Server

NewServer builds the perf MCP server: it registers the read-only tools and the runtime/metrics/pprof resources against a fresh *mcpsdk.Server carrying the "mecatl-perf" identity and the declarative instructions. A cpuGate is created per server so the CPU-profiling tools share one cooldown + in-flight guard.

It panics if a required dependency (Snapshot, Gatherer, Profiler) is nil — a wiring error the caller must fix, not a runtime condition to be handled.

Types

type AllocStat

type AllocStat struct {
	// Function is the allocating function's name.
	Function string `json:"function"`
	// File is the BASENAME of the allocating function's source file (path dropped).
	File string `json:"file"`
	// FlatBytes is the bytes attributed to this function alone.
	FlatBytes int64 `json:"flat_bytes"`
	// CumBytes is the bytes attributed to this function and its callees.
	CumBytes int64 `json:"cum_bytes"`
}

AllocStat is one ranked allocation row from a heap/allocs profile. It mirrors FuncStat but names the value bytes, since the memTop reducer ranks by an allocation-space sample index.

type CaptureCPUProfileInput

type CaptureCPUProfileInput struct {
	DurationSeconds int  `json:"duration_seconds,omitempty" jsonschema:"CPU profiling window in seconds (1-30, default 5)"`
	Limit           int  `json:"limit,omitempty" jsonschema:"number of top functions to return (1-50, default 15)"`
	IncludeRawLink  bool `` /* 147-byte string literal not displayed */
}

CaptureCPUProfileInput mirrors top_cpu_functions plus a raw-link flag.

type CaptureFlightRecorderInput

type CaptureFlightRecorderInput struct{}

CaptureFlightRecorderInput takes no parameters.

type CaptureFlightRecorderOutput

type CaptureFlightRecorderOutput struct {
	CapturedBytes int    `json:"captured_bytes"`
	WindowSummary string `json:"window_summary"`
}

CaptureFlightRecorderOutput summarizes a flight-recorder snapshot without returning the trace bytes.

type Deps

type Deps struct {
	// Snapshot returns the current runtime snapshot DTO. Required.
	Snapshot func() telemetry.RuntimeSnapshot
	// Gatherer gathers the prometheus metric families backing the latency
	// histograms and counters. *prometheus.Registry satisfies it. Required.
	Gatherer prometheus.Gatherer
	// Recorder is the flight recorder seam. Nil-able (recorder not armed).
	Recorder FlightRecorder
	// Profiler is the pprof seam. Required (defaultProfiler in production).
	Profiler Profiler
	// SlowTurns is the slow-turn history seam. Nil-able (history not enabled).
	SlowTurns SlowTurnSource
	// Clock returns the current time, for cooldown accounting and timestamps.
	// Nil falls back to time.Now.
	Clock func() time.Time
	// Logger is used for low-volume operational logging. It MUST NOT be used to
	// log tool inputs or outputs at info level. Nil falls back to a discard logger.
	Logger *slog.Logger
}

Deps is the dependency seam for the perf MCP server. Every external capability is an interface or a func so the adapter never reaches into a composition root and tests substitute fakes. *telemetry.FlightRecorder, *prometheus.Registry, and telemetry.Snapshot already satisfy the respective fields.

type FlightRecorder

type FlightRecorder interface {
	// SnapshotBytes returns the current trace window as parseable trace bytes.
	SnapshotBytes() ([]byte, error)
	// Enabled reports whether the recorder is currently running.
	Enabled() bool
}

FlightRecorder is the read seam over the execution-trace flight recorder. *telemetry.FlightRecorder satisfies it. It is nil-able: when the recorder was never armed, capture_flight_recorder returns a tool error rather than failing.

type FuncStat

type FuncStat struct {
	// Function is the function's name (pprof Function.Name).
	Function string `json:"function"`
	// File is the BASENAME of the function's source file — the absolute path is
	// dropped deliberately, because it leaks $HOME and workspace layout.
	File string `json:"file"`
	// FlatValue is the value attributed to this function alone (self time/space).
	FlatValue int64 `json:"flat_value"`
	// CumValue is the value attributed to this function and everything it called.
	CumValue int64 `json:"cum_value"`
}

FuncStat is one ranked function row from a CPU/timing profile. It carries the REDACTED function identity (name + file basename only) plus its flat and cumulative values in the profile's own unit (e.g. nanoseconds for a CPU profile). No absolute path and no pprof label survives into this struct.

type ListSlowTurnsInput

type ListSlowTurnsInput struct {
	ThresholdMs int64  `json:"threshold_ms,omitempty" jsonschema:"only turns at least this slow (ms); omit for the source default"`
	Limit       int    `json:"limit,omitempty" jsonschema:"max turns to return (1-20, default 10)"`
	Cursor      string `json:"cursor,omitempty" jsonschema:"opaque pagination cursor from a previous response's nextCursor"`
	Role        string `` /* 134-byte string literal not displayed */
}

ListSlowTurnsInput pages the slow-turn history.

type ListSlowTurnsOutput

type ListSlowTurnsOutput struct {
	Turns      []SlowTurn `json:"turns"`
	NextCursor string     `json:"nextCursor,omitempty"`
	TotalCount int        `json:"totalCount"`
}

ListSlowTurnsOutput is the paginated slow-turn page. Each turn carries numerics and timestamps only — never prompt text or session IDs.

type MemstatsProjection

type MemstatsProjection struct {
	// HeapAllocsTotalBytes is the cumulative heap-allocation counter (bytes ever
	// allocated since process start) — NOT live heap; live is HeapObjectBytes.
	HeapAllocsTotalBytes uint64 `json:"heap_allocs_total_bytes"`
	HeapObjects          uint64 `json:"heap_objects"`
	HeapObjectBytes      uint64 `json:"heap_object_bytes"`
	TotalMemoryBytes     uint64 `json:"total_memory_bytes"`
	RSSBytes             uint64 `json:"rss_bytes"`
	// Available echoes the snapshot's Available set so a consumer can tell which
	// runtime/metrics-derived fields were actually present this read.
	Available []string `json:"available"`
}

MemstatsProjection is the memory-focused superset view of the runtime snapshot served by perf://runtime/memstats. It is a projection of RuntimeSnapshot (NOT a runtime.ReadMemStats), so it inherits the snapshot's lock-free, no-STW guarantee. Byte counts are bytes.

type MetricSummaryEntry

type MetricSummaryEntry struct {
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Kind        string          `json:"kind"` // "histogram" | "scalar" | "absent"
	Count       uint64          `json:"count,omitempty"`
	Quantiles   []QuantileBound `json:"quantiles,omitempty"`
	Value       float64         `json:"value,omitempty"`
	ByRole      []RoleBreakdown `json:"by_role,omitempty"`
}

MetricSummaryEntry is one curated metric's reduced view in the metrics-summary resource: for a histogram, its count and quantile upper bounds; for a scalar (counter/gauge), its single value. Exactly one of Quantiles/Value is meaningful per Kind. ByRole, when present, is the bounded per-role breakdown (one row per role family observed on the family's series).

type Profiler

type Profiler interface {
	// Lookup returns the named profile's pprof bytes (debug=0, gzipped protobuf)
	// for name in {heap, goroutine, allocs, mutex, block}, or an error if the
	// name is unknown or the profile cannot be written.
	Lookup(name string) ([]byte, error)
	// CPUProfile runs a CPU profile for d and returns its pprof bytes. It blocks
	// for the duration. Only one CPU profile may run process-wide at a time; the
	// caller (the cpuGate) serializes access.
	CPUProfile(d time.Duration) ([]byte, error)
}

Profiler is the seam over runtime/pprof, so tests inject a deterministic profile rather than profiling the test binary. The production implementation (defaultProfiler) wraps runtime/pprof: Lookup for named heap/goroutine/etc. profiles, and a start/stop pair for the CPU profile.

func NewProfiler

func NewProfiler() Profiler

NewProfiler returns the production Profiler backed by runtime/pprof. Inject it into Deps.Profiler in the composition root; tests use a fake instead.

type QuantileBound

type QuantileBound struct {
	// Quantile is the requested rank in [0,1] (e.g. 0.99 for p99).
	Quantile float64 `json:"quantile"`
	// UpperBound is the upper bound of the bucket containing that rank, in the
	// histogram's own unit (seconds for the latency instruments).
	UpperBound float64 `json:"upper_bound"`
}

QuantileBound is one (quantile, upper-bound) pair derived from a histogram. The field is named UpperBound, not "value", to be HONEST about precision: it is the upper bound of the histogram bucket the quantile rank falls in, not an interpolated exact quantile.

type QueryMetricInput

type QueryMetricInput struct {
	MetricName string  `json:"metric_name,omitempty" jsonschema:"the curated metric to query; omit to list available metric names"`
	Quantile   float64 `` /* 133-byte string literal not displayed */
	Role       string  `` /* 150-byte string literal not displayed */
}

QueryMetricInput selects a curated metric to summarize. All fields optional: omitting metric_name lists the available names (discovery).

type QueryMetricOutput

type QueryMetricOutput struct {
	AvailableMetrics []string            `json:"available_metrics,omitempty"`
	Metric           *MetricSummaryEntry `json:"metric,omitempty"`
}

QueryMetricOutput is the structured result of query_metric. Exactly one shape is populated per call: AvailableMetrics for discovery, or the metric summary.

type RoleBreakdown

type RoleBreakdown struct {
	Role  string  `json:"role"`
	Count uint64  `json:"count,omitempty"`
	Value float64 `json:"value,omitempty"`
}

RoleBreakdown is one role family's share of a curated metric: the observation count for a histogram, or the summed value for a counter. The role dimension is intrinsically bounded (the closed roleFamilies set), so the breakdown can never grow past a handful of rows.

type SlowTurn

type SlowTurn struct {
	// TurnIndex is the monotonic index of the turn within its run.
	TurnIndex int `json:"turn_index"`
	// DurationMs is the turn's model-call wall-clock duration in milliseconds.
	DurationMs int64 `json:"duration_ms"`
	// TTFTMs is the time-to-first-token in milliseconds (0 if not measured).
	TTFTMs int64 `json:"ttft_ms"`
	// InterTokenMaxMs is the worst inter-token gap in milliseconds (0 if not measured).
	InterTokenMaxMs int64 `json:"inter_token_max_ms"`
	// EndedAt is the wall-clock time the turn ended, RFC3339.
	EndedAt time.Time `json:"ended_at"`
	// Role is the BOUNDED engine role family that produced the turn
	// (main|subagent|member|parallel|usermodel|child). It is a closed enum
	// label — never a def/member name or session id — so redaction by shape holds.
	Role string `json:"role,omitempty"`
}

SlowTurn is one entry of the per-turn slow-turn history. It carries ONLY numerics and timestamps — never prompt text, tool arguments, or session IDs — so list_slow_turns can never leak conversation content.

type SlowTurnSource

type SlowTurnSource interface {
	// Recent returns recent slow turns, newest first. thresholdMs, if > 0, filters
	// to turns at least that slow; 0 means "use the source default threshold". It
	// returns the whole (bounded) matching set so the tool can report an accurate
	// totalCount and paginate over a stable snapshot — the buffer's fixed size is
	// the cap, not a per-call limit argument.
	Recent(thresholdMs int64) []SlowTurn
}

SlowTurnSource is the read seam over the slow-turn ring buffer. It is nil-able: the concrete buffer lands in a later commit, so when nil, list_slow_turns reports that per-turn history is not enabled. The implementation owns the window/eviction policy; the buffer is intrinsically bounded (a fixed-size ring), so returning its whole contents is cheap.

type TopAllocationsInput

type TopAllocationsInput struct {
	Limit int `json:"limit,omitempty" jsonschema:"number of top allocating functions to return (1-50, default 15)"`
}

TopAllocationsInput configures the heap top-N.

type TopAllocationsOutput

type TopAllocationsOutput struct {
	TotalHeapBytes int64       `json:"total_heap_bytes"`
	Top            []AllocStat `json:"top"`
}

TopAllocationsOutput is the heap allocation summary.

type TopCPUFunctionsInput

type TopCPUFunctionsInput struct {
	DurationSeconds int `json:"duration_seconds,omitempty" jsonschema:"CPU profiling window in seconds (1-30, default 5)"`
	Limit           int `json:"limit,omitempty" jsonschema:"number of top functions to return (1-50, default 15)"`
}

TopCPUFunctionsInput configures a short CPU profile + top-N reduction.

type TopCPUFunctionsOutput

type TopCPUFunctionsOutput struct {
	DurationSeconds int        `json:"duration_seconds"`
	SampleCount     int        `json:"sample_count"`
	Top             []FuncStat `json:"top"`
}

TopCPUFunctionsOutput is the FuncStat-based result for the CPU tools.

Jump to

Keyboard shortcuts

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