Summary
Add automatic timing instrumentation to AimDB's execution primitives (.source(), .tap(), .link(), and future .transform()), enabling users to identify slow stages without manual instrumentation.
Motivation
AimDB owns the execution boundary for all user-provided callbacks. Since AimDB spawns and awaits these callbacks, it can automatically measure their execution time - giving users immediate visibility into which stage is the bottleneck without requiring any instrumentation code.
User story: "As an AimDB user, I want AimDB to tell me which .source(), .tap(), or .link() callback is slow, so I can identify bottlenecks without adding manual profiling code."
Important: Wall-Clock Time
Stage profiling measures wall-clock time (including await points), not CPU time. This is useful for identifying which stage is slow, but not why. Users should use tracing or tokio-console for detailed CPU vs I/O analysis.
Design
See full RFC: docs/design/014-M6-stage-profiling.md
Key Points
- Feature-flagged:
profiling feature, disabled by default (separate from metrics)
- Zero-cost when disabled: No timing overhead without feature
- Automatic instrumentation: AimDB times callbacks internally
- Builder pattern for names:
.source(...).with_name("camera_capture")
- MCP integration:
get_stage_profiling and reset_stage_profiling tools
Stage Metrics Tracked
call_count - number of invocations
total_time_ns - cumulative wall-clock time
avg_time_ns - average per invocation
min_time_ns - fastest invocation
max_time_ns - slowest invocation
Design Decisions
Tasks
Phase 1: Core Infrastructure
Phase 2: Instrumentation
Phase 3: Named Stages API
Phase 4: Metadata & MCP
Phase 5: Embassy Support
Phase 6: Testing & Documentation
Acceptance Criteria
-
With --features profiling enabled, get_stage_profiling MCP tool returns:
{
"record": "sensor::Temperature",
"stages": [
{
"stage_type": "source",
"index": 0,
"name": "sensor_reader",
"call_count": 1000,
"avg_time_ns": 5000000
},
{
"stage_type": "tap",
"index": 0,
"name": "data_processor",
"call_count": 1000,
"avg_time_ns": 40000000
}
],
"bottleneck": {
"stage_type": "tap",
"name": "data_processor",
"avg_time_ns": 40000000
}
}
-
Without profiling feature, no performance impact
-
Embassy adapter compiles for thumbv7em-none-eabihf with profiling enabled
-
All existing tests pass with and without profiling feature
-
Stage names set via .with_name() appear in MCP output
Example Usage
// Register source with profiling name
db.record::<Temperature>()
.source(|producer| async move {
loop {
let temp = read_sensor().await;
producer.produce(temp).await;
}
})
.with_name("sensor_reader");
// Register tap with profiling name
db.record::<Temperature>()
.tap(|value| async move {
process_temperature(value).await;
})
.with_name("data_processor");
Then query via MCP:
mcp_aimdb_get_stage_profiling(socket_path, "sensor::Temperature")
Out of Scope
- CPU time tracking (only wall-clock time)
- Histogram/percentile tracking (P50/P95/P99)
- External
StageProfiler API for user code
- Pipeline-level composition patterns
Related
- RFC:
docs/design/014-M6-stage-profiling.md
- Buffer metrics feature:
docs/design/013-M6-buffer-introspection-metrics.md
Summary
Add automatic timing instrumentation to AimDB's execution primitives (
.source(),.tap(),.link(), and future.transform()), enabling users to identify slow stages without manual instrumentation.Motivation
AimDB owns the execution boundary for all user-provided callbacks. Since AimDB spawns and awaits these callbacks, it can automatically measure their execution time - giving users immediate visibility into which stage is the bottleneck without requiring any instrumentation code.
User story: "As an AimDB user, I want AimDB to tell me which
.source(),.tap(), or.link()callback is slow, so I can identify bottlenecks without adding manual profiling code."Important: Wall-Clock Time
Stage profiling measures wall-clock time (including await points), not CPU time. This is useful for identifying which stage is slow, but not why. Users should use
tracingortokio-consolefor detailed CPU vs I/O analysis.Design
See full RFC:
docs/design/014-M6-stage-profiling.mdKey Points
profilingfeature, disabled by default (separate frommetrics).source(...).with_name("camera_capture")get_stage_profilingandreset_stage_profilingtoolsStage Metrics Tracked
call_count- number of invocationstotal_time_ns- cumulative wall-clock timeavg_time_ns- average per invocationmin_time_ns- fastest invocationmax_time_ns- slowest invocationDesign Decisions
.with_name("xxx")builder patternTasks
Phase 1: Core Infrastructure
StageMetricsstruct toaimdb-core/src/profiling/stage_metrics.rsprofilingfeature flag toaimdb-core/Cargo.tomlRecordProfilingMetricscontainer for per-record stage trackingStageMetrics(record, reset, accessors)Phase 2: Instrumentation
.source()execution inTypedRecord- wrap callback with timing.tap()execution in dispatcher - wrap handler with timing.link()execution in connector dispatch.transform()metrics collection (for future use)RecordProfilingMetricsper recordPhase 3: Named Stages API
.with_name(name: &str)builder method to source registration.with_name(name: &str)builder method to tap registration.with_name(name: &str)builder method to link registrationRecordProfilingMetricsPhase 4: Metadata & MCP
StageProfilingInfostruct toaimdb-core/src/remote/metadata.rsRecordMetadatawith optionalstage_profilingfieldcollect_metadata()to include profiling data when feature enabledget_stage_profilingMCP toolreset_stage_profilingMCP toolPhase 5: Embassy Support
portable-atomicdependency (conditional on profiling feature)StageMetricsworks withportable-atomic::AtomicU64cargo check --target thumbv7em-none-eabihf --features profilingPhase 6: Testing & Documentation
examples/remote-access-demomake allAcceptance Criteria
With
--features profilingenabled,get_stage_profilingMCP tool returns:{ "record": "sensor::Temperature", "stages": [ { "stage_type": "source", "index": 0, "name": "sensor_reader", "call_count": 1000, "avg_time_ns": 5000000 }, { "stage_type": "tap", "index": 0, "name": "data_processor", "call_count": 1000, "avg_time_ns": 40000000 } ], "bottleneck": { "stage_type": "tap", "name": "data_processor", "avg_time_ns": 40000000 } }Without profiling feature, no performance impact
Embassy adapter compiles for
thumbv7em-none-eabihfwith profiling enabledAll existing tests pass with and without profiling feature
Stage names set via
.with_name()appear in MCP outputExample Usage
Then query via MCP:
Out of Scope
StageProfilerAPI for user codeRelated
docs/design/014-M6-stage-profiling.mddocs/design/013-M6-buffer-introspection-metrics.md