1 //! Metrics client
2
3 use crate::adevice::Profiler;
4 use adevice_proto::clientanalytics::LogEvent;
5 use adevice_proto::clientanalytics::LogRequest;
6 use adevice_proto::user_log::adevice_log_event::AdeviceActionEvent;
7 use adevice_proto::user_log::adevice_log_event::AdeviceExitEvent;
8 use adevice_proto::user_log::adevice_log_event::AdeviceStartEvent;
9 use adevice_proto::user_log::AdeviceLogEvent;
10 use adevice_proto::user_log::Duration;
11
12 use anyhow::{anyhow, Result};
13 use std::env;
14 use std::fs;
15 use std::process::{Command, Stdio};
16 use std::time::UNIX_EPOCH;
17 use tracing::info;
18 use uuid::Uuid;
19
20 const ENV_OUT: &str = "OUT";
21 const ENV_USER: &str = "USER";
22 const ENV_TARGET: &str = "TARGET_PRODUCT";
23 const ENV_SURVEY_BANNER: &str = "ADEVICE_SURVEY_BANNER";
24 const METRICS_UPLOADER: &str = "/google/bin/releases/adevice-dev/metrics_uploader";
25 const ADEVICE_LOG_SOURCE: i32 = 2265;
26
27 pub trait MetricSender {
add_start_event(&mut self, command_line: &str, source_root: &str)28 fn add_start_event(&mut self, command_line: &str, source_root: &str);
add_action_event(&mut self, action: &str, duration: std::time::Duration)29 fn add_action_event(&mut self, action: &str, duration: std::time::Duration);
add_action_event_with_files_changed( &mut self, action: &str, duration: std::time::Duration, files_changed: std::vec::Vec<String>, )30 fn add_action_event_with_files_changed(
31 &mut self,
32 action: &str,
33 duration: std::time::Duration,
34 files_changed: std::vec::Vec<String>,
35 );
add_profiler_events(&mut self, profiler: &Profiler)36 fn add_profiler_events(&mut self, profiler: &Profiler);
add_exit_event(&mut self, output: &str, exit_code: i32)37 fn add_exit_event(&mut self, output: &str, exit_code: i32);
display_survey(&mut self)38 fn display_survey(&mut self);
39 }
40
41 #[derive(Debug, Clone)]
42 pub struct Metrics {
43 events: Vec<LogEvent>,
44 user: String,
45 invocation_id: String,
46 hostname: String,
47 }
48
49 impl MetricSender for Metrics {
add_start_event(&mut self, command_line: &str, source_root: &str)50 fn add_start_event(&mut self, command_line: &str, source_root: &str) {
51 let mut start_event = AdeviceStartEvent::default();
52 start_event.set_command_line(command_line.to_string());
53 start_event.set_source_root(source_root.to_string());
54 start_event.set_target(env::var(ENV_TARGET).unwrap_or("".to_string()));
55 start_event.set_hostname(self.hostname.to_string());
56
57 let mut event = self.default_log_event();
58 event.set_adevice_start_event(start_event);
59 self.events.push(LogEvent {
60 event_time_ms: Some(UNIX_EPOCH.elapsed().unwrap().as_millis() as i64),
61 source_extension: Some(protobuf::Message::write_to_bytes(&event).unwrap()),
62 ..Default::default()
63 });
64 }
65
add_action_event(&mut self, action: &str, duration: std::time::Duration)66 fn add_action_event(&mut self, action: &str, duration: std::time::Duration) {
67 self.add_action_event_with_files_changed(action, duration, Vec::new())
68 }
69
add_action_event_with_files_changed( &mut self, action: &str, duration: std::time::Duration, files_changed: std::vec::Vec<String>, )70 fn add_action_event_with_files_changed(
71 &mut self,
72 action: &str,
73 duration: std::time::Duration,
74 files_changed: std::vec::Vec<String>,
75 ) {
76 let action_event = AdeviceActionEvent {
77 action: Some(action.to_string()),
78 outcome: ::std::option::Option::None,
79 file_changed: files_changed,
80 duration: protobuf::MessageField::some(Duration {
81 seconds: Some(duration.as_secs() as i64),
82 nanos: Some(duration.as_nanos() as i32),
83 ..Default::default()
84 }),
85 ..Default::default()
86 };
87
88 let mut event: AdeviceLogEvent = self.default_log_event();
89 event.set_adevice_action_event(action_event);
90 self.events.push(LogEvent {
91 event_time_ms: Some(UNIX_EPOCH.elapsed().unwrap().as_millis() as i64),
92 source_extension: Some(protobuf::Message::write_to_bytes(&event).unwrap()),
93 ..Default::default()
94 });
95 }
96
add_exit_event(&mut self, output: &str, exit_code: i32)97 fn add_exit_event(&mut self, output: &str, exit_code: i32) {
98 let mut exit_event = AdeviceExitEvent::default();
99 exit_event.set_logs(output.to_string());
100 exit_event.set_exit_code(exit_code);
101
102 let mut event = self.default_log_event();
103 event.set_adevice_exit_event(exit_event);
104 self.events.push(LogEvent {
105 event_time_ms: Some(UNIX_EPOCH.elapsed().unwrap().as_millis() as i64),
106 source_extension: Some(protobuf::Message::write_to_bytes(&event).unwrap()),
107 ..Default::default()
108 });
109 }
110
add_profiler_events(&mut self, profiler: &Profiler)111 fn add_profiler_events(&mut self, profiler: &Profiler) {
112 self.add_action_event("device_fingerprint", profiler.device_fingerprint);
113 self.add_action_event("host_fingerprint", profiler.host_fingerprint);
114 self.add_action_event("ninja_deps_computer", profiler.ninja_deps_computer);
115 self.add_action_event("adb_cmds", profiler.adb_cmds);
116 self.add_action_event(&profiler.restart_type, profiler.restart);
117 self.add_action_event("wait_for_device", profiler.wait_for_device);
118 self.add_action_event("wait_for_boot_completed", profiler.wait_for_boot_completed);
119 self.add_action_event("first_remount_rw", profiler.first_remount_rw);
120 self.add_action_event("total", profiler.total);
121 // Compute the time we aren't capturing in a category.
122 // We could graph total, but sometimes it is easier to just graph this
123 // to see if we are missing significant chunks.
124 self.add_action_event(
125 "other",
126 profiler.total
127 - profiler.device_fingerprint
128 - profiler.host_fingerprint
129 - profiler.ninja_deps_computer
130 - profiler.adb_cmds
131 - profiler.restart
132 - profiler.wait_for_device
133 - profiler.wait_for_boot_completed
134 - profiler.first_remount_rw,
135 );
136 }
137
display_survey(&mut self)138 fn display_survey(&mut self) {
139 let survey = env::var(ENV_SURVEY_BANNER).unwrap_or("".to_string());
140 if !survey.is_empty() {
141 println!("\n{}", survey);
142 }
143 }
144 }
145
146 impl Default for Metrics {
default() -> Self147 fn default() -> Self {
148 Metrics {
149 events: Vec::new(),
150 user: env::var(ENV_USER).unwrap_or("".to_string()),
151 invocation_id: Uuid::new_v4().to_string(),
152 hostname: get_hostname(),
153 }
154 }
155 }
156
157 impl Metrics {
send(&self) -> Result<()>158 fn send(&self) -> Result<()> {
159 // Only send for internal users, check for metrics_uploader
160 if fs::metadata(METRICS_UPLOADER).is_err() {
161 return Err(anyhow!("Not internal user: Metrics not sent since uploader not found"));
162 }
163 if self.user.is_empty() {
164 return Err(anyhow!("USER env not set: Metrics not sent since no user set"));
165 }
166 // Serialize
167 let body = {
168 let mut log_request = LogRequest::default();
169 log_request.set_log_source(ADEVICE_LOG_SOURCE);
170
171 for e in &*self.events {
172 log_request.log_event.push(e.clone());
173 }
174 let res: Vec<u8> = protobuf::Message::write_to_bytes(&log_request).unwrap();
175 res
176 };
177
178 let out = env::var(ENV_OUT).unwrap_or("/tmp".to_string());
179 let temp_dir = format!("{}/adevice", out);
180 let temp_file_path = format!("{}/adevice/adevice.bin", out);
181 fs::create_dir_all(temp_dir).expect("Failed to create folder for metrics");
182 fs::write(temp_file_path.clone(), body).expect("Failed to write to metrics file");
183 if let Err(e) = Command::new(METRICS_UPLOADER)
184 .args([&temp_file_path])
185 .stdin(Stdio::null())
186 .stdout(Stdio::null())
187 .stderr(Stdio::null())
188 .spawn()
189 {
190 return Err(anyhow!("Failed to send metrics {}", e));
191 }
192 // TODO implement next_request_wait_millis that comes back in response
193
194 Ok(())
195 }
196
default_log_event(&self) -> AdeviceLogEvent197 fn default_log_event(&self) -> AdeviceLogEvent {
198 let mut event = AdeviceLogEvent::default();
199 event.set_user_key(self.user.to_string());
200 event.set_invocation_id(self.invocation_id.to_string());
201 event
202 }
203 }
204
get_hostname() -> String205 fn get_hostname() -> String {
206 Command::new("hostname").output().map_or_else(
207 |_err| String::new(),
208 |output| {
209 if output.status.success() {
210 String::from_utf8_lossy(&output.stdout).trim().to_string()
211 } else {
212 String::new()
213 }
214 },
215 )
216 }
217
218 impl Drop for Metrics {
drop(&mut self)219 fn drop(&mut self) {
220 match self.send() {
221 Ok(_) => (),
222 Err(e) => info!("Failed to send metrics: {}", e),
223 };
224 }
225 }
226
227 #[cfg(test)]
228 #[allow(unused)]
229 mod tests {
230 use super::*;
231
232 #[test]
test_print_events()233 fn test_print_events() {
234 let mut metrics = Metrics::default();
235 metrics.user = "test_user".to_string();
236 metrics.add_start_event("adevice status", "/home/test/aosp-main-with-phones");
237 metrics.add_start_event("adevice track SomeModule", "/home/test/aosp-main-with-phones");
238
239 assert_eq!(metrics.events.len(), 2);
240 metrics.send();
241 metrics.events.clear();
242 assert_eq!(metrics.events.len(), 0);
243 }
244 }
245