Skip to content

Commit 98bd4c9

Browse files
feat: add telemetry for agent config and tool execution timing (#2400)
1 parent 8aea885 commit 98bd4c9

File tree

10 files changed

+255
-46
lines changed

10 files changed

+255
-46
lines changed

crates/chat-cli/src/cli/agent/legacy/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ use crate::cli::agent::legacy::context::LegacyContextConfig;
2020
use crate::os::Os;
2121
use crate::util::directories;
2222

23-
pub async fn migrate(os: &mut Os) -> eyre::Result<Vec<Agent>> {
23+
/// Performs the migration from legacy profile configuration to agent configuration if it hasn't
24+
/// already been done.
25+
///
26+
/// Returns [Some] with the newly migrated agents if the migration was performed, [None] if the
27+
/// migration was already done previously.
28+
pub async fn migrate(os: &mut Os) -> eyre::Result<Option<Vec<Agent>>> {
2429
let has_migrated = os.database.get_has_migrated()?;
2530
if has_migrated.is_some_and(|has_migrated| has_migrated) {
26-
bail!("Nothing to migrate");
31+
return Ok(None);
2732
}
2833

2934
let legacy_global_context_path = directories::chat_global_context_path(os)?;
@@ -216,5 +221,5 @@ pub async fn migrate(os: &mut Os) -> eyre::Result<Vec<Agent>> {
216221

217222
os.database.set_has_migrated()?;
218223

219-
Ok(new_agents)
224+
Ok(Some(new_agents))
220225
}

crates/chat-cli/src/cli/agent/mod.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use thiserror::Error;
4343
use tokio::fs::ReadDir;
4444
use tracing::{
4545
error,
46+
info,
4647
warn,
4748
};
4849
use wrapper_types::ResourcePath;
@@ -382,12 +383,33 @@ impl Agents {
382383
/// agent selection
383384
/// * `skip_migration` - If true, skips migration of old profiles to new format
384385
/// * `output` - Writer for outputting warnings, errors, and status messages during loading
385-
pub async fn load(os: &mut Os, agent_name: Option<&str>, skip_migration: bool, output: &mut impl Write) -> Self {
386+
pub async fn load(
387+
os: &mut Os,
388+
agent_name: Option<&str>,
389+
skip_migration: bool,
390+
output: &mut impl Write,
391+
) -> (Self, AgentsLoadMetadata) {
392+
// Tracking metadata about the performed load operation.
393+
let mut load_metadata = AgentsLoadMetadata {
394+
launched_agent: agent_name.map(Into::into),
395+
..Default::default()
396+
};
397+
386398
let new_agents = if !skip_migration {
387399
match legacy::migrate(os).await {
388-
Ok(new_agents) => new_agents,
400+
Ok(Some(new_agents)) => {
401+
let migrated_count = new_agents.len();
402+
info!(migrated_count, "Profile migration successful");
403+
load_metadata.migration_performed = true;
404+
load_metadata.migrated_count = migrated_count as u32;
405+
new_agents
406+
},
407+
Ok(None) => {
408+
info!("Migration was not performed");
409+
vec![]
410+
},
389411
Err(e) => {
390-
warn!("Migration did not happen for the following reason: {e}. This is not necessarily an error");
412+
error!("Migration did not happen for the following reason: {e}");
391413
vec![]
392414
},
393415
}
@@ -421,6 +443,7 @@ impl Agents {
421443
match result {
422444
Ok(agent) => agents.push(agent),
423445
Err(e) => {
446+
load_metadata.load_failed_count += 1;
424447
let _ = queue!(
425448
output,
426449
style::SetForegroundColor(Color::Red),
@@ -458,6 +481,7 @@ impl Agents {
458481
match result {
459482
Ok(agent) => agents.push(agent),
460483
Err(e) => {
484+
load_metadata.load_failed_count += 1;
461485
let _ = queue!(
462486
output,
463487
style::SetForegroundColor(Color::Red),
@@ -665,11 +689,14 @@ impl Agents {
665689
}
666690
}
667691

668-
Self {
669-
agents,
670-
active_idx,
671-
..Default::default()
672-
}
692+
(
693+
Self {
694+
agents,
695+
active_idx,
696+
..Default::default()
697+
},
698+
load_metadata,
699+
)
673700
}
674701

675702
/// Returns a label to describe the permission status for a given tool.
@@ -717,6 +744,16 @@ impl Agents {
717744
}
718745
}
719746

747+
/// Metadata from the executed [Agents::load] operation.
748+
#[derive(Debug, Clone, Default)]
749+
pub struct AgentsLoadMetadata {
750+
pub migration_performed: bool,
751+
pub migrated_count: u32,
752+
pub load_count: u32,
753+
pub load_failed_count: u32,
754+
pub launched_agent: Option<String>,
755+
}
756+
720757
async fn load_agents_from_entries(
721758
mut files: ReadDir,
722759
os: &Os,

crates/chat-cli/src/cli/agent/root_command_args.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl AgentArgs {
7575
let mut stderr = std::io::stderr();
7676
match self.cmd {
7777
Some(AgentSubcommands::List) | None => {
78-
let agents = Agents::load(os, None, true, &mut stderr).await;
78+
let agents = Agents::load(os, None, true, &mut stderr).await.0;
7979
let agent_with_path =
8080
agents
8181
.agents
@@ -100,7 +100,7 @@ impl AgentArgs {
100100
writeln!(stderr, "{}", output_str)?;
101101
},
102102
Some(AgentSubcommands::Create { name, directory, from }) => {
103-
let mut agents = Agents::load(os, None, true, &mut stderr).await;
103+
let mut agents = Agents::load(os, None, true, &mut stderr).await.0;
104104
let path_with_file_name = create_agent(os, &mut agents, name.clone(), directory, from).await?;
105105
let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
106106
let mut cmd = std::process::Command::new(editor_cmd);
@@ -131,7 +131,7 @@ impl AgentArgs {
131131
)?;
132132
},
133133
Some(AgentSubcommands::Rename { agent, new_name }) => {
134-
let mut agents = Agents::load(os, None, true, &mut stderr).await;
134+
let mut agents = Agents::load(os, None, true, &mut stderr).await.0;
135135
rename_agent(os, &mut agents, agent.clone(), new_name.clone()).await?;
136136
writeln!(stderr, "\n✓ Renamed agent '{}' to '{}'\n", agent, new_name)?;
137137
},

crates/chat-cli/src/cli/chat/cli/profile.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl AgentSubcommand {
133133
.map_err(|e| ChatError::Custom(format!("Error printing agent schema: {e}").into()))?;
134134
},
135135
Self::Create { name, directory, from } => {
136-
let mut agents = Agents::load(os, None, true, &mut session.stderr).await;
136+
let mut agents = Agents::load(os, None, true, &mut session.stderr).await.0;
137137
let path_with_file_name = create_agent(os, &mut agents, name.clone(), directory, from)
138138
.await
139139
.map_err(|e| ChatError::Custom(Cow::Owned(e.to_string())))?;
@@ -183,7 +183,7 @@ impl AgentSubcommand {
183183
)?;
184184
},
185185
Self::Rename { agent, new_name } => {
186-
let mut agents = Agents::load(os, None, true, &mut session.stderr).await;
186+
let mut agents = Agents::load(os, None, true, &mut session.stderr).await.0;
187187
rename_agent(os, &mut agents, agent.clone(), new_name.clone())
188188
.await
189189
.map_err(|e| ChatError::Custom(Cow::Owned(e.to_string())))?;

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ use std::io::{
3030
};
3131
use std::process::ExitCode;
3232
use std::sync::Arc;
33-
use std::time::Duration;
33+
use std::time::{
34+
Duration,
35+
Instant,
36+
};
3437

3538
use amzn_codewhisperer_client::types::SubscriptionStatus;
3639
use clap::{
@@ -138,6 +141,7 @@ use crate::database::settings::Setting;
138141
use crate::mcp_client::Prompt;
139142
use crate::os::Os;
140143
use crate::telemetry::core::{
144+
AgentConfigInitArgs,
141145
ChatAddedMessageParams,
142146
ChatConversationType,
143147
MessageMetaTag,
@@ -240,11 +244,26 @@ impl ChatArgs {
240244
)?;
241245
}
242246

247+
let conversation_id = uuid::Uuid::new_v4().to_string();
248+
info!(?conversation_id, "Generated new conversation id");
249+
243250
let agents = {
244251
let skip_migration = self.no_interactive;
245-
let mut agents = Agents::load(os, self.agent.as_deref(), skip_migration, &mut stderr).await;
252+
let (mut agents, md) = Agents::load(os, self.agent.as_deref(), skip_migration, &mut stderr).await;
246253
agents.trust_all_tools = self.trust_all_tools;
247254

255+
os.telemetry
256+
.send_agent_config_init(&os.database, conversation_id.clone(), AgentConfigInitArgs {
257+
agents_loaded_count: md.load_count as i64,
258+
agents_loaded_failed_count: md.load_failed_count as i64,
259+
legacy_profile_migration_executed: md.migration_performed,
260+
legacy_profile_migrated_count: md.migrated_count as i64,
261+
launched_agent: md.launched_agent,
262+
})
263+
.await
264+
.map_err(|err| error!(?err, "failed to send agent config init telemetry"))
265+
.ok();
266+
248267
if agents
249268
.get_active()
250269
.is_some_and(|a| !a.mcp_servers.mcp_servers.is_empty())
@@ -287,8 +306,6 @@ impl ChatArgs {
287306
None
288307
};
289308

290-
let conversation_id = uuid::Uuid::new_v4().to_string();
291-
info!(?conversation_id, "Generated new conversation id");
292309
let (prompt_request_sender, prompt_request_receiver) = std::sync::mpsc::channel::<Option<String>>();
293310
let (prompt_response_sender, prompt_response_receiver) = std::sync::mpsc::channel::<Vec<String>>();
294311
let mut tool_manager = ToolManagerBuilder::default()
@@ -495,10 +512,17 @@ pub struct ChatSession {
495512
spinner: Option<Spinner>,
496513
/// [ConversationState].
497514
conversation: ConversationState,
515+
/// Tool uses requested by the model that are actively being handled.
498516
tool_uses: Vec<QueuedTool>,
517+
/// An index into [Self::tool_uses] to represent the current tool use being handled.
518+
pending_tool_index: Option<usize>,
519+
/// The time immediately after having received valid tool uses from the model.
520+
///
521+
/// Used to track the time taken from initially prompting the user to tool execute
522+
/// completion.
523+
tool_turn_start_time: Option<Instant>,
499524
/// [RequestMetadata] about the ongoing operation.
500525
user_turn_request_metadata: Vec<RequestMetadata>,
501-
pending_tool_index: Option<usize>,
502526
/// Telemetry events to be sent as part of the conversation. The HashMap key is tool_use_id.
503527
tool_use_telemetry_events: HashMap<String, ToolUseEventBuilder>,
504528
/// State used to keep track of tool use relation
@@ -622,6 +646,7 @@ impl ChatSession {
622646
tool_uses: vec![],
623647
user_turn_request_metadata: vec![],
624648
pending_tool_index: None,
649+
tool_turn_start_time: None,
625650
tool_use_telemetry_events: HashMap::new(),
626651
tool_use_status: ToolUseStatus::Idle,
627652
failed_request_ids: Vec::new(),
@@ -978,6 +1003,7 @@ impl ChatSession {
9781003
self.conversation.enforce_conversation_invariants();
9791004
self.conversation.reset_next_user_message();
9801005
self.pending_tool_index = None;
1006+
self.tool_turn_start_time = None;
9811007
self.reset_user_turn();
9821008

9831009
self.inner = Some(ChatState::PromptUser {
@@ -1806,6 +1832,9 @@ impl ChatSession {
18061832

18071833
if allowed {
18081834
tool.accepted = true;
1835+
self.tool_use_telemetry_events
1836+
.entry(tool.id.clone())
1837+
.and_modify(|ev| ev.is_trusted = true);
18091838
continue;
18101839
}
18111840

@@ -1821,10 +1850,12 @@ impl ChatSession {
18211850
let mut image_blocks: Vec<RichImageBlock> = Vec::new();
18221851

18231852
for tool in &self.tool_uses {
1853+
let tool_start = std::time::Instant::now();
18241854
let mut tool_telemetry = self.tool_use_telemetry_events.entry(tool.id.clone());
1825-
tool_telemetry = tool_telemetry.and_modify(|ev| ev.is_accepted = true);
1855+
tool_telemetry = tool_telemetry.and_modify(|ev| {
1856+
ev.is_accepted = true;
1857+
});
18261858

1827-
let tool_start = std::time::Instant::now();
18281859
let invoke_result = tool.tool.invoke(os, &mut self.stdout).await;
18291860

18301861
if self.spinner.is_some() {
@@ -1837,12 +1868,18 @@ impl ChatSession {
18371868
}
18381869
execute!(self.stdout, style::Print("\n"))?;
18391870

1840-
let tool_time = std::time::Instant::now().duration_since(tool_start);
1871+
let tool_end_time = Instant::now();
1872+
let tool_time = tool_end_time.duration_since(tool_start);
1873+
tool_telemetry = tool_telemetry.and_modify(|ev| {
1874+
ev.execution_duration = Some(tool_time);
1875+
ev.turn_duration = self.tool_turn_start_time.map(|t| tool_end_time.duration_since(t));
1876+
});
18411877
if let Tool::Custom(ct) = &tool.tool {
18421878
tool_telemetry = tool_telemetry.and_modify(|ev| {
1879+
ev.is_custom_tool = true;
1880+
// legacy fields previously implemented for only MCP tools
18431881
ev.custom_tool_call_latency = Some(tool_time.as_secs() as usize);
18441882
ev.input_token_size = Some(ct.get_input_token_size());
1845-
ev.is_custom_tool = true;
18461883
});
18471884
}
18481885
let tool_time = format!("{}.{}", tool_time.as_secs(), tool_time.subsec_millis());
@@ -2232,6 +2269,7 @@ impl ChatSession {
22322269
} else {
22332270
self.tool_uses.clear();
22342271
self.pending_tool_index = None;
2272+
self.tool_turn_start_time = None;
22352273

22362274
self.send_chat_telemetry(os, TelemetryResult::Succeeded, None, None, None, true)
22372275
.await;
@@ -2342,6 +2380,7 @@ impl ChatSession {
23422380

23432381
self.tool_uses = queued_tools;
23442382
self.pending_tool_index = Some(0);
2383+
self.tool_turn_start_time = Some(Instant::now());
23452384
Ok(ChatState::ExecuteTools)
23462385
}
23472386

@@ -2353,6 +2392,7 @@ impl ChatSession {
23532392
self.conversation.enforce_conversation_invariants();
23542393
self.conversation.reset_next_user_message();
23552394
self.pending_tool_index = None;
2395+
self.tool_turn_start_time = None;
23562396
return Ok(ChatState::PromptUser {
23572397
skip_printing_tools: false,
23582398
});

0 commit comments

Comments
 (0)