Problem
Local gf workspace jobs can feel slow, but today there is no coherent timing record showing whether time was spent queued, waiting for workspace access, computing, reading/writing, syncing, publishing, downloading, verifying, or retrying.
Objective
Establish one Rust-owned, disabled-by-default OpenTelemetry runtime and versioned local-job schema that every gf workspace job uses to explain where wall time went.
Requirements
- Expose one Rust-owned telemetry module through
graphforge-api; CLI/Python/Node only project configuration and context.
- Define a versioned local-job schema with finite job families, stages, wait reasons, outcomes, and failure classes.
- Every job records enqueue, start, and finish boundaries so queue delay and active wall time are distinct.
- Standard stage fields: wall duration, explicit wait duration, exact bytes/records when already known, attempt count, and normalized outcome.
- Own providers in an explicit runtime handle; never replace or require host-global tracing.
- Support disabled/noop, deterministic in-memory tests, and explicit local development export (OTLP-compatible local collector and/or structured stdout/file exporter selected during implementation).
- No automatic remote export, environment-variable activation, DNS, or network activity.
- Bound queues, batches, flush, and shutdown. Telemetry failure never slows, fails, retries, or changes a workspace job.
- Job correlation may use an ephemeral per-run trace/job identifier in spans, but never as a metric label and never reuse repository, project, graph, generation, operation, or user identifiers.
- Prohibit paths, repository names, query text/parameters, property data, credentials, manifests, digests, and graph/result content.
Acceptance Criteria
- One public Rust configuration enables local workspace-job traces without taking over host-global telemetry.
- Disabled mode proves no exporter thread/network activity and negligible job-path overhead.
- A deterministic job fixture reports
queue_delay + active_duration consistently with total elapsed time and attributes active time to documented finite stages without overlapping/double counting.
- In-memory export proves stable names, units, enum values, nesting, and the deny-by-default attribute set.
- Full queues, exporter failure, flush timeout, and repeated shutdown are bounded and cannot alter job timing controls, results, or errors.
- Thin CLI/Python/Node projections use the same Rust schema and lifecycle.
BDD Completion Scenarios
Scenario: A developer profiles a slow local job
Given local telemetry is explicitly enabled
When a workspace job completes
Then its trace distinguishes queue delay, active time, finite stage durations, waits, attempts, and outcome.
Scenario: Telemetry is disabled
Given normal local gf use
When workspace jobs run
Then no exporter or network activity occurs and results are unchanged.
Scenario: Local export fails
Given the local collector or output sink is unavailable
When a job runs and telemetry shuts down
Then the job remains unchanged and telemetry exits within configured bounds.
Testing
In-memory trace/metric snapshots; elapsed-time accounting invariants; disabled overhead; host-global coexistence; saturation/failure/flush/shutdown; privacy canaries; binding/CLI parity; Cargo/Bazel gates.
Documentation
Document local enablement, schema, timing math, lifecycle, privacy, and how to inspect a single slow job.
Non-Goals
Managed-cloud usage accounting, billing, tenant/project dashboards, hosted collection, production APM, raw-query analytics, or remote export by default.
Relationships
Foundation for #887, #888, and #922; local profiling guidance closes through #889.
Operation Telemetry Contract
Model a local workspace job as an ordered trace of GraphForge API operations, not an inventory of internal components.
- Each span records a stable, bounded API operation name, its order through parent/child relationships, wall duration, outcome, and attempts.
- Record process or thread CPU time for an operation when the platform provides an authoritative measurement. Keep wall time and CPU time separate; mark compute unavailable rather than estimating it.
- Record an invoked extension's stable extension ID and version on trace spans. Extension values are excluded from metric labels unless promoted to a reviewed bounded allowlist. The contract must support XYG as the expected first extension without hard-coding XYG semantics into core telemetry.
- Import/export spans record direction, format or protocol, validation/encoding/decoding/transfer/publication phases, and exact bytes/records/batches when known.
- API arguments, queries, payloads, graph/property data, paths, URIs, credentials, and arbitrary error text are prohibited.
Acceptance requires an ordered trace with deterministic operation timings, authoritative CPU accounting or an explicit unavailable result, extension invocation coverage using a test extension, and import/export fixtures that reconcile phases and exact counts.
Span composition invariant
- A span represents one command or operation from start through exactly one completed outcome; it is not a point event, component inventory entry, or arbitrary code section.
- A command composed of multiple operations has one parent span for the completed command and ordered child spans for its completed operations. An operation that composes further work follows the same rule recursively.
- Sequential order is derived from the child spans' timing under their common parent. Concurrent child operations may overlap. Events may annotate a span but never replace operation spans.
- Parent duration covers the end-to-end command/operation; child durations explain its work and may not be added blindly when children overlap.
Tests must prove single-operation, sequential nested, recursively nested, concurrent-child, failure, and cancellation shapes, with exactly one terminal outcome per span.
Fast workspace statistics
Provide one Rust-owned workspace-statistics operation that returns a consistent point-in-time snapshot quickly enough for interactive CLI use. Periodic telemetry samples this operation; telemetry is not the only way to obtain the values.
- Return exact workspace-wide node and edge counts plus other stable top-level counts already owned by storage, and the GraphForge-owned on-disk workspace footprint in bytes.
- Maintain or derive these values from bounded metadata. The read must not scan graph records, Parquet contents, or recursively walk the workspace directory.
- Define which GraphForge-owned files and generations contribute to disk size. Exclude unrelated files even if they share the parent volume.
- Update counters and byte accounting transactionally with commit, import, compaction/checkpoint, publication, recovery, and deletion. Recovery must detect or reconcile stale accounting without making the normal stats read expensive.
- Expose the same semantics through thin CLI, Python, and Node projections. The stats operation receives its own completed operation span when telemetry is enabled.
- Keep logical data size, workspace disk footprint, and volume capacity/free space as distinct fields. Capacity fields may be unavailable by platform; exact counts and GraphForge workspace footprint are required for a valid workspace.
Acceptance requires exact before/after reconciliation across mutation, import, export-without-mutation, checkpoint/compaction, reopen/recovery, and deletion; a deterministic no-scan test; bounded latency evidence on a large fixture; and proof that bindings return the Rust result without recomputation.
Problem
Local
gfworkspace jobs can feel slow, but today there is no coherent timing record showing whether time was spent queued, waiting for workspace access, computing, reading/writing, syncing, publishing, downloading, verifying, or retrying.Objective
Establish one Rust-owned, disabled-by-default OpenTelemetry runtime and versioned local-job schema that every
gfworkspace job uses to explain where wall time went.Requirements
graphforge-api; CLI/Python/Node only project configuration and context.Acceptance Criteria
queue_delay + active_durationconsistently with total elapsed time and attributes active time to documented finite stages without overlapping/double counting.BDD Completion Scenarios
Scenario: A developer profiles a slow local job
Given local telemetry is explicitly enabled
When a workspace job completes
Then its trace distinguishes queue delay, active time, finite stage durations, waits, attempts, and outcome.
Scenario: Telemetry is disabled
Given normal local
gfuseWhen workspace jobs run
Then no exporter or network activity occurs and results are unchanged.
Scenario: Local export fails
Given the local collector or output sink is unavailable
When a job runs and telemetry shuts down
Then the job remains unchanged and telemetry exits within configured bounds.
Testing
In-memory trace/metric snapshots; elapsed-time accounting invariants; disabled overhead; host-global coexistence; saturation/failure/flush/shutdown; privacy canaries; binding/CLI parity; Cargo/Bazel gates.
Documentation
Document local enablement, schema, timing math, lifecycle, privacy, and how to inspect a single slow job.
Non-Goals
Managed-cloud usage accounting, billing, tenant/project dashboards, hosted collection, production APM, raw-query analytics, or remote export by default.
Relationships
Foundation for #887, #888, and #922; local profiling guidance closes through #889.
Operation Telemetry Contract
Model a local workspace job as an ordered trace of GraphForge API operations, not an inventory of internal components.
Acceptance requires an ordered trace with deterministic operation timings, authoritative CPU accounting or an explicit unavailable result, extension invocation coverage using a test extension, and import/export fixtures that reconcile phases and exact counts.
Span composition invariant
Tests must prove single-operation, sequential nested, recursively nested, concurrent-child, failure, and cancellation shapes, with exactly one terminal outcome per span.
Fast workspace statistics
Provide one Rust-owned workspace-statistics operation that returns a consistent point-in-time snapshot quickly enough for interactive CLI use. Periodic telemetry samples this operation; telemetry is not the only way to obtain the values.
Acceptance requires exact before/after reconciliation across mutation, import, export-without-mutation, checkpoint/compaction, reopen/recovery, and deletion; a deterministic no-scan test; bounded latency evidence on a large fixture; and proof that bindings return the Rust result without recomputation.