Skip to content

Commit 48647e1

Browse files
authored
feat(telemetry): add client application, aws service and operation name (#436)
1 parent affacdb commit 48647e1

File tree

10 files changed

+143
-29
lines changed

10 files changed

+143
-29
lines changed

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,18 @@ impl ChatSession {
15991599
let mut tool_telemetry = self.tool_use_telemetry_events.entry(tool.id.clone());
16001600
tool_telemetry = tool_telemetry.and_modify(|ev| ev.is_accepted = true);
16011601

1602+
// Extract AWS service name and operation name if available
1603+
if let Some(additional_info) = tool.tool.get_additional_info() {
1604+
if let Some(aws_service_name) = additional_info.get("aws_service_name").and_then(|v| v.as_str()) {
1605+
tool_telemetry =
1606+
tool_telemetry.and_modify(|ev| ev.aws_service_name = Some(aws_service_name.to_string()));
1607+
}
1608+
if let Some(aws_operation_name) = additional_info.get("aws_operation_name").and_then(|v| v.as_str()) {
1609+
tool_telemetry =
1610+
tool_telemetry.and_modify(|ev| ev.aws_operation_name = Some(aws_operation_name.to_string()));
1611+
}
1612+
}
1613+
16021614
let tool_start = std::time::Instant::now();
16031615
let invoke_result = tool.tool.invoke(os, &mut self.stdout).await;
16041616

@@ -2179,7 +2191,7 @@ impl ChatSession {
21792191
}
21802192
.map(|v| v.to_string());
21812193

2182-
os.telemetry.send_tool_use_suggested(event).ok();
2194+
os.telemetry.send_tool_use_suggested(&os.database, event).await.ok();
21832195
}
21842196
}
21852197

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ use crate::cli::chat::tools::{
8686
ToolOrigin,
8787
ToolSpec,
8888
};
89+
use crate::database::Database;
8990
use crate::database::settings::Setting;
9091
use crate::mcp_client::{
9192
JsonRpcResponse,
@@ -415,6 +416,7 @@ impl ToolManagerBuilder {
415416
let pending_clone = pending.clone();
416417
let (mut msg_rx, messenger_builder) = ServerMessengerBuilder::new(20);
417418
let telemetry_clone = os.telemetry.clone();
419+
let database_clone = os.database.clone();
418420
let notify = Arc::new(Notify::new());
419421
let notify_weak = Arc::downgrade(&notify);
420422
let load_record = Arc::new(Mutex::new(HashMap::<String, Vec<LoadingRecord>>::new()));
@@ -452,7 +454,9 @@ impl ToolManagerBuilder {
452454
&mut sanitized_mapping,
453455
&regex,
454456
&telemetry_clone,
455-
);
457+
&database_clone,
458+
)
459+
.await;
456460
if let Some(sender) = &loading_status_sender_clone {
457461
// Anomalies here are not considered fatal, thus we shall give
458462
// warnings.
@@ -591,7 +595,8 @@ impl ToolManagerBuilder {
591595
Err(e) => {
592596
error!("Error initializing mcp client for server {}: {:?}", name, &e);
593597
os.telemetry
594-
.send_mcp_server_init(conversation_id.clone(), Some(e.to_string()), 0)
598+
.send_mcp_server_init(&os.database, conversation_id.clone(), Some(e.to_string()), 0)
599+
.await
595600
.ok();
596601
let _ = messenger.send_tools_list_result(Err(e)).await;
597602
},
@@ -1266,13 +1271,14 @@ impl ToolManager {
12661271
}
12671272

12681273
#[inline]
1269-
fn process_tool_specs(
1274+
async fn process_tool_specs(
12701275
conversation_id: &str,
12711276
server_name: &str,
12721277
specs: &mut Vec<ToolSpec>,
12731278
tn_map: &mut HashMap<String, String>,
12741279
regex: &Regex,
12751280
telemetry: &TelemetryThread,
1281+
database: &Database,
12761282
) -> eyre::Result<()> {
12771283
// Each mcp server might have multiple tools.
12781284
// To avoid naming conflicts we are going to namespace it.
@@ -1322,7 +1328,10 @@ fn process_tool_specs(
13221328
specs.retain(|spec| !matches!(spec.tool_origin, ToolOrigin::Native));
13231329
// Send server load success metric datum
13241330
let conversation_id = conversation_id.to_string();
1325-
let _ = telemetry.send_mcp_server_init(conversation_id, None, number_of_tools);
1331+
telemetry
1332+
.send_mcp_server_init(database, conversation_id, None, number_of_tools)
1333+
.await
1334+
.ok();
13261335
// Tool name translation. This is beyond of the scope of what is
13271336
// considered a "server load". Reasoning being:
13281337
// - Failures here are not related to server load

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ impl Tool {
129129
Tool::Thinking(think) => think.validate(os).await,
130130
}
131131
}
132+
133+
/// Returns additional information about the tool if available
134+
pub fn get_additional_info(&self) -> Option<serde_json::Value> {
135+
match self {
136+
Tool::UseAws(use_aws) => Some(use_aws.get_additional_info()),
137+
// Add other tool types here as they implement get_additional_info()
138+
_ => None,
139+
}
140+
}
132141
}
133142

134143
#[derive(Debug, Clone)]

crates/chat-cli/src/cli/chat/tools/use_aws.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ impl UseAws {
173173
Ok(())
174174
}
175175

176+
pub fn get_additional_info(&self) -> serde_json::Value {
177+
serde_json::json!({
178+
"aws_service_name": self.service_name.clone(),
179+
"aws_operation_name": self.operation_name.clone()
180+
})
181+
}
182+
176183
/// Returns the CLI arguments properly formatted as kebab case if parameters is
177184
/// [Option::Some], otherwise None
178185
fn cli_parameters(&self) -> Option<Vec<(String, String)>> {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ impl RootSubcommand {
137137

138138
// Send executed telemetry.
139139
if self.valid_for_telemetry() {
140-
os.telemetry.send_cli_subcommand_executed(&self).ok();
140+
os.telemetry
141+
.send_cli_subcommand_executed(&os.database, &self)
142+
.await
143+
.ok();
141144
}
142145

143146
match self {

crates/chat-cli/src/telemetry/core.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub struct Event {
4242
pub created_time: Option<SystemTime>,
4343
pub credential_start_url: Option<String>,
4444
pub sso_region: Option<String>,
45+
pub client_application: Option<String>,
4546
#[serde(flatten)]
4647
pub ty: EventType,
4748
}
@@ -53,6 +54,7 @@ impl Event {
5354
created_time: Some(SystemTime::now()),
5455
credential_start_url: None,
5556
sso_region: None,
57+
client_application: None,
5658
}
5759
}
5860

@@ -64,6 +66,10 @@ impl Event {
6466
self.sso_region = Some(sso_region);
6567
}
6668

69+
pub fn set_client_application(&mut self, client_application: String) {
70+
self.client_application = Some(client_application);
71+
}
72+
6773
pub fn into_metric_datum(self) -> Option<MetricDatum> {
6874
match self.ty {
6975
EventType::UserLoggedIn {} => Some(
@@ -100,6 +106,7 @@ impl Event {
100106
credential_start_url: self.credential_start_url.map(Into::into),
101107
codewhispererterminal_subcommand: Some(subcommand.into()),
102108
codewhispererterminal_in_cloudshell: None,
109+
codewhispererterminal_client_application: self.client_application.map(Into::into),
103110
}
104111
.into_metric_datum(),
105112
),
@@ -152,6 +159,7 @@ impl Event {
152159
reason_desc: reason_desc.map(Into::into),
153160
status_code: status_code.map(|v| v as i64).map(Into::into),
154161
codewhispererterminal_model: model.map(Into::into),
162+
codewhispererterminal_client_application: self.client_application.map(Into::into),
155163
}
156164
.into_metric_datum(),
157165
),
@@ -169,6 +177,8 @@ impl Event {
169177
output_token_size,
170178
custom_tool_call_latency,
171179
model,
180+
aws_service_name,
181+
aws_operation_name,
172182
} => Some(
173183
CodewhispererterminalToolUseSuggested {
174184
create_time: self.created_time,
@@ -190,6 +200,9 @@ impl Event {
190200
codewhispererterminal_custom_tool_latency: custom_tool_call_latency
191201
.map(|l| CodewhispererterminalCustomToolLatency(l as i64)),
192202
codewhispererterminal_model: model.map(Into::into),
203+
codewhispererterminal_client_application: self.client_application.map(Into::into),
204+
codewhispererterminal_aws_service_name: aws_service_name.map(Into::into),
205+
codewhispererterminal_aws_operation_name: aws_operation_name.map(Into::into),
193206
}
194207
.into_metric_datum(),
195208
),
@@ -208,6 +221,7 @@ impl Event {
208221
codewhispererterminal_tools_per_mcp_server: Some(CodewhispererterminalToolsPerMcpServer(
209222
number_of_tools as i64,
210223
)),
224+
codewhispererterminal_client_application: self.client_application.map(Into::into),
211225
}
212226
.into_metric_datum(),
213227
),
@@ -266,6 +280,7 @@ impl Event {
266280
reason: reason.map(Into::into),
267281
reason_desc: reason_desc.map(Into::into),
268282
status_code: status_code.map(|v| v as i64).map(Into::into),
283+
codewhispererterminal_client_application: self.client_application.map(Into::into),
269284
}
270285
.into_metric_datum(),
271286
),
@@ -320,6 +335,8 @@ pub enum EventType {
320335
output_token_size: Option<usize>,
321336
custom_tool_call_latency: Option<usize>,
322337
model: Option<String>,
338+
aws_service_name: Option<String>,
339+
aws_operation_name: Option<String>,
323340
},
324341
McpServerInit {
325342
conversation_id: String,
@@ -364,6 +381,8 @@ pub struct ToolUseEventBuilder {
364381
pub output_token_size: Option<usize>,
365382
pub custom_tool_call_latency: Option<usize>,
366383
pub model: Option<String>,
384+
pub aws_service_name: Option<String>,
385+
pub aws_operation_name: Option<String>,
367386
}
368387

369388
impl ToolUseEventBuilder {
@@ -382,6 +401,8 @@ impl ToolUseEventBuilder {
382401
output_token_size: None,
383402
custom_tool_call_latency: None,
384403
model,
404+
aws_service_name: None,
405+
aws_operation_name: None,
385406
}
386407
}
387408

crates/chat-cli/src/telemetry/definitions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mod tests {
3232
reason_desc: None,
3333
status_code: None,
3434
codewhispererterminal_model: None,
35+
codewhispererterminal_client_application: None,
3536
});
3637

3738
let s = serde_json::to_string_pretty(&metric_datum_init).unwrap();

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

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub use crate::telemetry::core::{
6666
QProfileSwitchIntent,
6767
TelemetryResult,
6868
};
69+
use crate::util::env_var::Q_CLI_CLIENT_APPLICATION;
6970
use crate::util::system_info::os_version;
7071

7172
#[derive(thiserror::Error, Debug)]
@@ -226,10 +227,17 @@ impl TelemetryThread {
226227
Ok(self.tx.send(Event::new(EventType::UserLoggedIn {}))?)
227228
}
228229

229-
pub fn send_cli_subcommand_executed(&self, subcommand: &RootSubcommand) -> Result<(), TelemetryError> {
230-
Ok(self.tx.send(Event::new(EventType::CliSubcommandExecuted {
230+
pub async fn send_cli_subcommand_executed(
231+
&self,
232+
database: &Database,
233+
subcommand: &RootSubcommand,
234+
) -> Result<(), TelemetryError> {
235+
let mut telemetry_event = Event::new(EventType::CliSubcommandExecuted {
231236
subcommand: subcommand.to_string(),
232-
}))?)
237+
});
238+
set_event_metadata(database, &mut telemetry_event).await;
239+
240+
Ok(self.tx.send(telemetry_event)?)
233241
}
234242

235243
#[allow(clippy::too_many_arguments)] // TODO: Should make a parameters struct.
@@ -246,7 +254,7 @@ impl TelemetryThread {
246254
status_code: Option<u16>,
247255
model: Option<String>,
248256
) -> Result<(), TelemetryError> {
249-
let mut event = Event::new(EventType::ChatAddedMessage {
257+
let mut telemetry_event = Event::new(EventType::ChatAddedMessage {
250258
conversation_id,
251259
message_id,
252260
request_id,
@@ -257,13 +265,17 @@ impl TelemetryThread {
257265
status_code,
258266
model,
259267
});
260-
set_start_url_and_region(database, &mut event).await;
268+
set_event_metadata(database, &mut telemetry_event).await;
261269

262-
Ok(self.tx.send(event)?)
270+
Ok(self.tx.send(telemetry_event)?)
263271
}
264272

265-
pub fn send_tool_use_suggested(&self, event: ToolUseEventBuilder) -> Result<(), TelemetryError> {
266-
Ok(self.tx.send(Event::new(EventType::ToolUseSuggested {
273+
pub async fn send_tool_use_suggested(
274+
&self,
275+
database: &Database,
276+
event: ToolUseEventBuilder,
277+
) -> Result<(), TelemetryError> {
278+
let mut telemetry_event = Event::new(EventType::ToolUseSuggested {
267279
conversation_id: event.conversation_id,
268280
utterance_id: event.utterance_id,
269281
user_input_id: event.user_input_id,
@@ -277,20 +289,29 @@ impl TelemetryThread {
277289
output_token_size: event.output_token_size,
278290
custom_tool_call_latency: event.custom_tool_call_latency,
279291
model: event.model,
280-
}))?)
292+
aws_service_name: event.aws_service_name,
293+
aws_operation_name: event.aws_operation_name,
294+
});
295+
set_event_metadata(database, &mut telemetry_event).await;
296+
297+
Ok(self.tx.send(telemetry_event)?)
281298
}
282299

283-
pub fn send_mcp_server_init(
300+
pub async fn send_mcp_server_init(
284301
&self,
302+
database: &Database,
285303
conversation_id: String,
286304
init_failure_reason: Option<String>,
287305
number_of_tools: usize,
288306
) -> Result<(), TelemetryError> {
289-
Ok(self.tx.send(Event::new(crate::telemetry::EventType::McpServerInit {
307+
let mut telemetry_event = Event::new(crate::telemetry::EventType::McpServerInit {
290308
conversation_id,
291309
init_failure_reason,
292310
number_of_tools,
293-
}))?)
311+
});
312+
set_event_metadata(database, &mut telemetry_event).await;
313+
314+
Ok(self.tx.send(telemetry_event)?)
294315
}
295316

296317
pub fn send_did_select_profile(
@@ -336,28 +357,33 @@ impl TelemetryThread {
336357
reason_desc: Option<String>,
337358
status_code: Option<u16>,
338359
) -> Result<(), TelemetryError> {
339-
let mut event = Event::new(EventType::MessageResponseError {
360+
let mut telemetry_event = Event::new(EventType::MessageResponseError {
340361
result,
341362
reason,
342363
reason_desc,
343364
status_code,
344365
conversation_id,
345366
context_file_length,
346367
});
347-
set_start_url_and_region(database, &mut event).await;
368+
set_event_metadata(database, &mut telemetry_event).await;
348369

349-
Ok(self.tx.send(event)?)
370+
Ok(self.tx.send(telemetry_event)?)
350371
}
351372
}
352373

353-
async fn set_start_url_and_region(database: &Database, event: &mut Event) {
374+
async fn set_event_metadata(database: &Database, event: &mut Event) {
354375
let (start_url, region) = get_start_url_and_region(database).await;
355376
if let Some(start_url) = start_url {
356377
event.set_start_url(start_url);
357378
}
358379
if let Some(region) = region {
359380
event.set_sso_region(region);
360381
}
382+
383+
// Set the client application from environment variable
384+
if let Ok(client_app) = std::env::var(Q_CLI_CLIENT_APPLICATION) {
385+
event.set_client_application(client_app);
386+
}
361387
}
362388

363389
#[derive(Debug)]
@@ -627,7 +653,8 @@ mod test {
627653

628654
thread.send_user_logged_in().ok();
629655
thread
630-
.send_cli_subcommand_executed(&RootSubcommand::Version { changelog: None })
656+
.send_cli_subcommand_executed(&database, &RootSubcommand::Version { changelog: None })
657+
.await
631658
.ok();
632659
thread
633660
.send_chat_added_message(

0 commit comments

Comments
 (0)