1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Functions for creating and collecting atoms.
16 
17 use crate::aidl::{clone_file, GLOBAL_SERVICE};
18 use crate::crosvm::VmMetric;
19 use crate::get_calling_uid;
20 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
21 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
22     CpuTopology::CpuTopology,
23     IVirtualMachine::IVirtualMachine,
24     VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
25     VirtualMachineConfig::VirtualMachineConfig,
26 };
27 use android_system_virtualizationservice::binder::{Status, Strong};
28 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
29     AtomVmBooted::AtomVmBooted,
30     AtomVmCreationRequested::AtomVmCreationRequested,
31     AtomVmExited::AtomVmExited,
32 };
33 use anyhow::{anyhow, Result};
34 use binder::ParcelFileDescriptor;
35 use log::{info, warn};
36 use microdroid_payload_config::VmPayloadConfig;
37 use statslog_virtualization_rust::vm_creation_requested;
38 use std::thread;
39 use std::time::{Duration, SystemTime};
40 use zip::ZipArchive;
41 
42 const INVALID_NUM_CPUS: i32 = -1;
43 
get_apex_list(config: &VirtualMachineAppConfig) -> String44 fn get_apex_list(config: &VirtualMachineAppConfig) -> String {
45     match &config.payload {
46         Payload::PayloadConfig(_) => String::new(),
47         Payload::ConfigPath(config_path) => {
48             let vm_payload_config = get_vm_payload_config(&config.apk, config_path);
49             if let Ok(vm_payload_config) = vm_payload_config {
50                 vm_payload_config
51                     .apexes
52                     .iter()
53                     .map(|x| x.name.clone())
54                     .collect::<Vec<String>>()
55                     .join(":")
56             } else {
57                 "INFO: Can't get VmPayloadConfig".to_owned()
58             }
59         }
60     }
61 }
62 
get_vm_payload_config( apk_fd: &Option<ParcelFileDescriptor>, config_path: &str, ) -> Result<VmPayloadConfig>63 fn get_vm_payload_config(
64     apk_fd: &Option<ParcelFileDescriptor>,
65     config_path: &str,
66 ) -> Result<VmPayloadConfig> {
67     let apk = apk_fd.as_ref().ok_or_else(|| anyhow!("APK is none"))?;
68     let apk_file = clone_file(apk)?;
69     let mut apk_zip = ZipArchive::new(&apk_file)?;
70     let config_file = apk_zip.by_name(config_path)?;
71     let vm_payload_config: VmPayloadConfig = serde_json::from_reader(config_file)?;
72     Ok(vm_payload_config)
73 }
74 
get_duration(vm_start_timestamp: Option<SystemTime>) -> Duration75 fn get_duration(vm_start_timestamp: Option<SystemTime>) -> Duration {
76     match vm_start_timestamp {
77         Some(vm_start_timestamp) => vm_start_timestamp.elapsed().unwrap_or_default(),
78         None => Duration::default(),
79     }
80 }
81 
82 // Returns the number of CPUs configured in the host system.
83 // This matches how crosvm determines the number of logical cores.
84 // For telemetry purposes only.
get_num_cpus() -> Option<usize>85 pub(crate) fn get_num_cpus() -> Option<usize> {
86     // SAFETY: Only integer constants passed back and forth.
87     let ret = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) };
88     if ret > 0 {
89         ret.try_into().ok()
90     } else {
91         None
92     }
93 }
94 
95 /// Write the stats of VMCreation to statsd
96 /// The function creates a separate thread which waits for statsd to start to push atom
write_vm_creation_stats( config: &VirtualMachineConfig, is_protected: bool, ret: &binder::Result<Strong<dyn IVirtualMachine>>, )97 pub fn write_vm_creation_stats(
98     config: &VirtualMachineConfig,
99     is_protected: bool,
100     ret: &binder::Result<Strong<dyn IVirtualMachine>>,
101 ) {
102     let creation_succeeded;
103     let binder_exception_code;
104     match ret {
105         Ok(_) => {
106             creation_succeeded = true;
107             binder_exception_code = Status::ok().exception_code() as i32;
108         }
109         Err(ref e) => {
110             creation_succeeded = false;
111             binder_exception_code = e.exception_code() as i32;
112         }
113     }
114     let (vm_identifier, config_type, cpu_topology, memory_mib, apexes) = match config {
115         VirtualMachineConfig::AppConfig(config) => (
116             config.name.clone(),
117             vm_creation_requested::ConfigType::VirtualMachineAppConfig,
118             config.cpuTopology,
119             config.memoryMib,
120             get_apex_list(config),
121         ),
122         VirtualMachineConfig::RawConfig(config) => (
123             config.name.clone(),
124             vm_creation_requested::ConfigType::VirtualMachineRawConfig,
125             config.cpuTopology,
126             config.memoryMib,
127             String::new(),
128         ),
129     };
130 
131     let num_cpus: i32 = match cpu_topology {
132         CpuTopology::MATCH_HOST => {
133             get_num_cpus().and_then(|v| v.try_into().ok()).unwrap_or_else(|| {
134                 warn!("Failed to determine the number of CPUs in the host");
135                 INVALID_NUM_CPUS
136             })
137         }
138         _ => 1,
139     };
140 
141     let atom = AtomVmCreationRequested {
142         uid: get_calling_uid() as i32,
143         vmIdentifier: vm_identifier,
144         isProtected: is_protected,
145         creationSucceeded: creation_succeeded,
146         binderExceptionCode: binder_exception_code,
147         configType: config_type as i32,
148         numCpus: num_cpus,
149         memoryMib: memory_mib,
150         apexes,
151     };
152 
153     info!("Writing VmCreationRequested atom into statsd.");
154     thread::spawn(move || {
155         GLOBAL_SERVICE.atomVmCreationRequested(&atom).unwrap_or_else(|e| {
156             warn!("Failed to write VmCreationRequested atom: {e}");
157         });
158     });
159 }
160 
161 /// Write the stats of VM boot to statsd
162 /// The function creates a separate thread which waits for statsd to start to push atom
write_vm_booted_stats( uid: i32, vm_identifier: &str, vm_start_timestamp: Option<SystemTime>, )163 pub fn write_vm_booted_stats(
164     uid: i32,
165     vm_identifier: &str,
166     vm_start_timestamp: Option<SystemTime>,
167 ) {
168     let vm_identifier = vm_identifier.to_owned();
169     let duration = get_duration(vm_start_timestamp);
170 
171     let atom = AtomVmBooted {
172         uid,
173         vmIdentifier: vm_identifier,
174         elapsedTimeMillis: duration.as_millis() as i64,
175     };
176 
177     info!("Writing VmBooted atom into statsd.");
178     thread::spawn(move || {
179         GLOBAL_SERVICE.atomVmBooted(&atom).unwrap_or_else(|e| {
180             warn!("Failed to write VmBooted atom: {e}");
181         });
182     });
183 }
184 
185 /// Write the stats of VM exit to statsd
write_vm_exited_stats_sync( uid: i32, vm_identifier: &str, reason: DeathReason, exit_signal: Option<i32>, vm_metric: &VmMetric, )186 pub fn write_vm_exited_stats_sync(
187     uid: i32,
188     vm_identifier: &str,
189     reason: DeathReason,
190     exit_signal: Option<i32>,
191     vm_metric: &VmMetric,
192 ) {
193     let vm_identifier = vm_identifier.to_owned();
194     let elapsed_time_millis = get_duration(vm_metric.start_timestamp).as_millis() as i64;
195     let guest_time_millis = vm_metric.cpu_guest_time.unwrap_or_default();
196     let rss = vm_metric.rss.unwrap_or_default();
197 
198     let atom = AtomVmExited {
199         uid,
200         vmIdentifier: vm_identifier,
201         elapsedTimeMillis: elapsed_time_millis,
202         deathReason: reason,
203         guestTimeMillis: guest_time_millis,
204         rssVmKb: rss.vm,
205         rssCrosvmKb: rss.crosvm,
206         exitSignal: exit_signal.unwrap_or_default(),
207     };
208 
209     info!("Writing VmExited atom into statsd.");
210     GLOBAL_SERVICE.atomVmExited(&atom).unwrap_or_else(|e| {
211         warn!("Failed to write VmExited atom: {e}");
212     });
213 }
214