telemetry: additional metrics (error, consistent start/success) - #778
Conversation
|
Azure Pipelines: 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 Changes recommended
CLI failure telemetry remains incomplete, and ACL detection failures are incorrectly reported as negative results.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds expanded telemetry for command failures, servicing lifecycle events, and startup context.
Changes:
- Adds correlated
command_errormetrics for CLI and gRPC operations. - Adds manual rollback and runtime-update lifecycle metrics.
- Enriches startup telemetry with ACL detection and system uptime.
File summaries
| File | Description |
|---|---|
Cargo.toml |
Enables Nix time APIs. |
docs/Reference/Agent-Configuration.md |
Documents command-error telemetry. |
crates/trident/src/main.rs |
Instruments CLI servicing commands. |
crates/trident/src/lib.rs |
Re-exports instrumentation and enriches startup metrics. |
crates/trident/src/logging/operation_context.rs |
Implements and tests command-error reporting. |
crates/trident/src/server/tridentserver/mod.rs |
Instruments gRPC servicing commands. |
crates/trident/src/engine/runtime_update.rs |
Emits runtime-update success. |
crates/trident/src/engine/manual_rollback/mod.rs |
Emits rollback start and runtime success. |
crates/trident/src/engine/manual_rollback/utils.rs |
Makes rollback request kinds loggable. |
crates/trident_api/src/error.rs |
Exposes error-origin locations. |
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
a97f856 to
a2e921d
Compare
There was a problem hiding this comment.
🟡 Changes recommended
CLI startup events lose correlation metadata, and runtime-update success is omitted from persisted metrics archives.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 2
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Correlation-ID pre-warming creates missing datastores and bypasses the host-provisioning guard for several CLI commands.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 1
- Review effort level: Balanced
f6f0da6 to
5e7b215
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Initial install/update telemetry can remain uncorrelated, and runtime rollback persistence is duplicated.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
crates/trident/src/main.rs:202
- Issue: The missing-datastore check also skips pre-warming for
installandupdate, so the first such CLI invocation still emitscommand_startwithout acorrelation_id. Evidence: Those commands are intentionally exempt from the provisioning guard below, andTrident::newcreates the ID only afterrun_commandhas already emittedcommand_start. Suggestion: Skip creation only for commands that require an existing datastore; allow install/update to create and attach the ID before enteringrun_command.
if !agent_config.datastore_path().exists() {
return Ok(None);
- Files reviewed: 10/10 changed files
- Comments generated: 2
- Review effort level: Balanced
5e7b215 to
e6ac4c4
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Several non-servicing CLI failures still bypass the promised command_error telemetry.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 1
- Review effort level: Balanced
e6ac4c4 to
79b5d01
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Read-only and response-encoded gRPC failures remain outside the new command-error instrumentation.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 1
- Review effort level: Balanced
79b5d01 to
ee0bf7c
Compare
… command_error Addresses findings from deep review of PR #778: - grpc-client subcommands (install/update/rollback, not just read-only ones) now always append to the metrics file instead of truncating it, since grpc-client never owns the file's lifecycle -- the daemon does. Fixes the truncate-vs-append race where a servicing grpc-client command could wipe out metrics the daemon was concurrently appending. - grpc-client only fires its own command_error for genuine transport failures (TridentClientError::ConnectionError) now, via a new run_command_if() primitive alongside run_command(). When the daemon actually responded (including outright rejections), the daemon's own command_error already covers the failure with full kind/subkind/location fidelity, so the client stays silent instead of double-emitting a less-informative duplicate. - manual_rollback_start now fires once per logical manual rollback (gated on allowed_operations.has_stage()) instead of once per execute_rollback() call, so finalize-only resume calls no longer inflate the count. - finalize_update's auto-rollback outcome is now persisted to the archived metrics/log record unconditionally, instead of being skipped when the auto-rollback itself failed -- so a failed auto-rollback is no longer invisible in later archived investigation. - Telemetry.md documents that command_error covers pre-dispatch rejections (not just in-handler failures), and that grpc-client's command_error is now scoped to transport failures only. Adds unit tests for run_command_if's should_report gating. Not addressed: command_error kind/subkind/location fidelity for grpc-client-issued commands staying at a fixed internal classification -- descoped, since the only remaining grpc-client command_error emissions are transport failures, for which kind=internal is already accurate.
The command_error documentation added in the prior commit accidentally converted the whole file from CRLF to LF line endings (a byte-vs-text mode file-write mistake). No content changed here, only line endings, restoring consistency with the rest of the repository's CRLF-using docs.
…nstall() best-effort telemetry)
There was a problem hiding this comment.
🟢 Approval recommended
The telemetry paths, failure classification, persistence ordering, and regression coverage are coherent with no unresolved blocking issue identified.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Fixes one test call site missing the truncate arg added by this branch's own make_trace_sender_with_metrics_path signature change.
….rs rustfmt drift
There was a problem hiding this comment.
🟡 Changes recommended
Command coverage, gRPC timing, transport classification, and archived failure telemetry remain inconsistent.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 4
- Review effort level: Balanced
… into grpc_client dispatch Pulls in PR774 commit 83ce2cb (OperationSource enum distinguishing cli/daemon/grpc-client telemetry sources) and threads it through run_command/run_command_if/run_reboot_command. - All 4 real call sites now tag a source: main.rs x2 (Cli), tridentserver/mod.rs (Daemon), grpc_client/mod.rs (GrpcClient) -- the first place OperationSource::GrpcClient becomes real. - services/mod.rs reject_invalid_argument/reject_invalid_field also tagged Daemon (daemon rejecting a request before it reaches a handler). - run_reboot_command needs no source argument: it reuses a previously captured CapturedOperation which already carries its original source.
There was a problem hiding this comment.
🟡 Changes recommended
Several command paths remain uninstrumented, and gRPC-client telemetry currently starts after the operation completes.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
crates/trident/src/grpc_client/mod.rs:41
- Issue: The client operation finishes before its command wrapper starts. Evidence:
runtime.block_on(run_client(args))performs the connection and complete RPC first; only afterward doesrun_command_ifemitcommand_start, so the start timestamp is after completion, client-side events lack the operation context, and a panic inrun_clientbypasses panic reporting. Suggestion: executeruntime.block_on(run_client(args))inside the wrapper closure and derive the reporting decision from the resulting wrapped error.
let command = args.command.name().replace('-', "_");
let client_result = runtime.block_on(run_client(args));
crates/trident/src/grpc_client/mod.rs:57
- Issue: This predicate does not recognize the documented “connection dropped mid-call” case. Evidence: it only matches
ConnectionError, which is created during the initialconnect; failures after connection are wrapped asRequestError(Status)orResponseError(Status)intridentclient.rs, so they produce no client-sidecommand_erroreven though the daemon may never have reported one. Suggestion: classify transport-originated request/stream statuses separately from statuses actually returned by the daemon and report the former.
let is_transport_failure = client_result.as_ref().err().is_some_and(|e| {
e.chain().any(|cause| {
matches!(
cause.downcast_ref::<TridentClientError>(),
Some(TridentClientError::ConnectionError(..))
)
- Files reviewed: 20/20 changed files
- Comments generated: 5
- Review effort level: Balanced
stream_image_start fires with no matching completion signal. Added stream_image_success, fired in Trident::stream_image() once the underlying install() call returns Ok, mirroring the existing _start metric. (clean_install_finalized was considered for the streaming clean-install completion gap too, but dropped -- the clean_install/finalize_clean_install functions are already #[tracing::instrument]-ed spans, which already give start/end + duration for free; an additional boolean metric would just be redundant with that.)
There was a problem hiding this comment.
🔵 Needs a closer look
Several command paths remain uninstrumented, and gRPC-client telemetry is emitted after execution with incomplete transport-failure classification.
Review details
Suppressed comments (8)
Previously missed (3) — in code that hasn't changed since the last review.
crates/trident/src/grpc_client/mod.rs:40
- Issue: The new gRPC-client
commandtag does not identify the RPC actually dispatched. Evidence:ClientCommands::name()returns compatibility names such asclient-update, and this also ignoresallowed_operations; a stage-only update is sent asupdate_stagebyrun_clientbut tagged here asclient_update, unlike the daemon/direct-CLIupdate_stageconvention. Suggestion: derive the telemetry name from the selected RPC/stage-finalize operations and rely onsource = grpc-clientrather than encoding the client incommand.
crates/trident/src/main.rs:190 - Issue:
command_startstill lacksdatabase_ideven when an existing datastore already has one. Evidence: this preflight attaches onlyinstallation_id;attach_database_id_if_presentis not called untilTrident::new(lib.rs:286-294), afterrun_commandhas emitted the start event. The daemon startup/per-request refresh has the same omission, despite Telemetry.md stating every event includesdatabase_id. Suggestion: pre-attach the existing datastore's database ID alongside the installation ID before emitting command telemetry in both entry points.
crates/trident/src/server/tridentserver/mod.rs:211 - Issue: Per-request telemetry can attach an installation ID from a different datastore than the request actually uses. Evidence: servicing handlers use the
self.agent_configloaded for this server, but this helper reloads configuration from disk; if the configured datastore path changes while the daemon is running, telemetry refreshes from the new path while the request still operates on the original one. A reload failure also needlessly skips refresh despite the server already having valid configuration. Suggestion: use the server's existing configuration.
docs/Reference/Telemetry.md:53
- Issue: This documented guarantee is not implemented for all command failures. Evidence:
run_tridentdeliberately returns fromvalidate,get,diagnose,offline-initialize,rollback --check, andstart-networkbeforerun_command(main.rs:65-147), and daemonreading_requestlikewise executes failures without the wrapper (server/tridentserver/mod.rs:390-407). This reintroduces the coverage gaps that earlier revisions addressed and conflicts with the PR description's “whenever a command fails.” Suggestion: Route these command paths through the error wrapper, or narrow both the PR contract and this documentation to the subset that is actually instrumented.
If a command fails, a `command_error` event is also sent (tagged with the
same `operation_id`/`command` as above), breaking the failure down into:
crates/trident/src/grpc_client/mod.rs:41
- Issue:
command_startis emitted only after the gRPC-client command has already completed. Evidence:runtime.block_on(run_client(args))runs beforerun_command_if, so the operation context is absent throughout connection/request processing; a panic there also bypassesrun_command_if's panic reporting. Suggestion: Executerun_clientinside the command wrapper so start/error telemetry brackets the real invocation.
let command = args.command.name().replace('-', "_");
let client_result = runtime.block_on(run_client(args));
crates/trident/src/grpc_client/mod.rs:57
- Issue: The transport predicate misses the documented “dropped mid-call” case. Evidence: a failure while awaiting the initial RPC is wrapped as
TridentClientError::RequestError, and a stream read failure is wrapped asResponseError(tridentclient.rs:242-249,269-284); this match recognizes only the initialconnect()failure. Those dropped-call failures therefore produce no client-sidecommand_error, even though the daemon may never have reported one. Suggestion: classify transport-derived request/response statuses as transport failures while continuing to suppress statuses confirmed to be daemon responses.
let is_transport_failure = client_result.as_ref().err().is_some_and(|e| {
e.chain().any(|cause| {
matches!(
cause.downcast_ref::<TridentClientError>(),
Some(TridentClientError::ConnectionError(..))
)
crates/trident/src/server/tridentserver/services/mod.rs:48
- Issue: The new telemetry location for every missing request field points to this shared helper rather than the service call site that detected the missing field. Evidence:
TridentError::newis#[track_caller], but this wrapper is not, so all commands collapse toservices/mod.rs:50; for fields within the same command,kind,subkind, andlocationthen become identical. Suggestion: propagate the caller location through this helper.
fn reject_invalid_argument(
&self,
command: &str,
field: &str,
message: impl Into<String>,
) -> Status {
crates/trident/src/server/tridentserver/services/mod.rs:82
- Issue: Invalid-field telemetry similarly records this helper as the error origin instead of the rejecting service call site. Evidence: the tracked
TridentError::newcall receives this untracked wrapper as its immediate caller, so all parse failures reportservices/mod.rs:84. Suggestion: propagate the original caller location.
fn reject_invalid_field(
&self,
command: &str,
field: &str,
reason: impl Into<String>,
message: impl Into<String>,
) -> Status {
- Files reviewed: 20/20 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…al, grpc_client command bracketing + transport classification, track_caller on reject helpers, doc wording
There was a problem hiding this comment.
🟡 Changes recommended
Error telemetry currently misclassifies daemon admission responses and includes excluded read-only operations.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 4
- Review effort level: Balanced
…-telemetry' into user/bfjelds/mjolnir/command-error-metric
- grpc-client: gate command_error on the command actually being a servicing one (install/update/commit/rebuild-raid/rollback/stream-disk), not every ClientCommands variant -- a read-only client-version transport failure was incorrectly reported as command_error, exceeding the servicing-only contract. - grpc-client: is_transport_failure incorrectly assumed the daemon never deliberately returns Code::Unavailable. try_acquire_read_lock/ try_acquire_write_lock/servicing_request/reading_requests admission- control rejections do exactly that (connection-lock or servicing-lock contention), bypassing trident_error_to_status. Introduce CONNECTION_LOCK_BUSY_MESSAGE/SERVICING_LOCK_BUSY_MESSAGE constants shared between the daemon (that constructs them) and the client (that now excludes them from the transport-failure check), so a busy-daemon retry response is no longer misreported as a client-side command_error. - validate_host_configuration: its missing-config-field rejection went through reject_invalid_argument, which always emits command_start/ command_error -- inconsistent with every other outcome of this documented read-only, lock-free RPC. Return Status::invalid_argument directly instead, matching its pre-existing untelemetered behavior. - docs: add stream_disk to the Command Errors sections servicing-command list; its handler already goes through servicing_request the same as install/update/etc.
…source - grpc_client: Install/Update commands now use stage/finalize-granular telemetry naming (install_stage/install_finalize/update_stage/ update_finalize) via the shared command_name() helper, matching CLI naming instead of a flat client_update/client_install compat name. command_name() moved from main.rs into logging::operation_context so both the CLI (main.rs) and the library grpc_client module can share it. - tridentserver::refresh_ids: use the already-loaded self.agent_config instead of reloading AgentConfig::load() from disk on every refresh, avoiding a wrong-datastore-path risk if the on-disk config changes at runtime and silently skipping the refresh on a reload failure. Addresses 2 remaining valid findings from suppressed PR778 review 5147457633 (grpc-client command naming granularity; tridentserver refresh_ids datastore source).
There was a problem hiding this comment.
🟡 Changes recommended
Command names remain inconsistent across entry points, and some daemon-generated servicing failures bypass error telemetry.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 2
- Review effort level: Balanced
…g database_id refs in this branch
There was a problem hiding this comment.
🟡 Changes recommended
Reboot error reporting can leak its deduplication flag and suppress a later command’s error metric.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
…OR_REPORTED leak in run_with_captured_operation
There was a problem hiding this comment.
🟡 Changes recommended
Invalid-request handlers perform potentially blocking SQLite refreshes before entering their blocking sections.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
crates/trident/src/server/tridentserver/services/mod.rs:94
- Issue: This second rejection path also performs synchronous SQLite refresh work before entering
block_in_place. Evidence:refresh_idsmay block for the configured five-second busy timeout, so concurrent invalid-URL requests can stall the async runtime workers. Suggestion: perform the refresh as the first operation inside the existing blocking section.
self.refresh_ids();
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
…nside block_in_place reject_invalid_argument and reject_invalid_field called self.refresh_ids() before entering block_in_place, but refresh_ids does its own synchronous SQLite I/O (DataStore::open sets a 5s busy timeout), so it needs the same "about to block" signal to the Tokio runtime as run_command. Moved the call inside the block_in_place closure at both sites.
There was a problem hiding this comment.
🟡 Changes recommended
Mixed append and non-append writers can overwrite concurrent gRPC-client metrics and corrupt the JSONL file.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
…; fix concurrent-writer metrics file corruption TraceSender ' s truncating (daemon-startup) metrics-file handle used files::create_file (File::create, O_TRUNC without O_APPEND) and kept it open for the sender ' s lifetime. A concurrent grpc-client sender (opened separately in append mode) extending the file past this handle ' s stale write offset would have its data overwritten the next time the daemon wrote a metric. Reset the file ' s content up front (when truncate is requested) via a one-off File::create, then always keep the real, long-lived handle open in append-only mode -- OpenOptions::truncate(true) can ' t be combined with .append(true) in one call because the standard library requires .write(true) for truncation, which would defeat append-only semantics for every later write.
There was a problem hiding this comment.
🟡 Changes recommended
Read-only grpc-client rollback --check transport failures are incorrectly reported as servicing command errors.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
…shot fixes); fix rollback --check misclassified as servicing command
is_servicing_client_command matched Rollback { .. } unconditionally, so
a transport failure during read-only rollback --check was classified
as a servicing-command failure and emitted command_error, unlike other
read-only commands (client-version, etc). Match only the non-check
case: Rollback { check: false, .. }.
Merge conflict in main.rs: this branch had already refactored the CLI
servicing path to wrap the whole match (preflight, Trident::new, and
the actual command) in a single run_command call, computing command/
config_path/agent_config-prewarm up front. Kept that structure and
applied the incoming AgentConfig load-once fix within it, instead of
reloading AgentConfig a second time inside the closure.
There was a problem hiding this comment.
🟡 Changes recommended
The public telemetry documentation incorrectly claims all daemon rejections already emit command_error.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
Implement (
manual_rollback_start,runtime_update_success,manual_rollback_runtime_success,stream_image_success) to be consistent with other operation metrics.Adds a new
command_errormetric that fires whenever a command fails, for both CLI and gRPC/daemon, included in metric:kind: top-level error category (e.g.internal,invalid-input,servicing)subkind: the specific error within that category, when one applieslocation:file:linewhere the error was originally raisedRelated PRs in stack: