• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     CpuOptions::CpuOptions,
23     CpuOptions::CpuTopology::CpuTopology,
24     IVirtualMachine::IVirtualMachine,
25     VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
26     VirtualMachineConfig::VirtualMachineConfig,
27 };
28 use android_system_virtualizationservice::binder::{Status, Strong};
29 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
30     AtomVmBooted::AtomVmBooted,
31     AtomVmCreationRequested::AtomVmCreationRequested,
32     AtomVmExited::AtomVmExited,
33 };
34 use anyhow::{anyhow, Result};
35 use binder::ParcelFileDescriptor;
36 use log::{info, warn};
37 use microdroid_payload_config::VmPayloadConfig;
38 use statslog_virtualization_rust::vm_creation_requested;
39 use std::thread;
40 use std::time::{Duration, SystemTime};
41 use zip::ZipArchive;
42 
43 const INVALID_NUM_CPUS: i32 = -1;
44 
get_apex_list(config: &VirtualMachineAppConfig) -> String45 fn get_apex_list(config: &VirtualMachineAppConfig) -> String {
46     match &config.payload {
47         Payload::PayloadConfig(_) => String::new(),
48         Payload::ConfigPath(config_path) => {
49             let vm_payload_config = get_vm_payload_config(&config.apk, config_path);
50             if let Ok(vm_payload_config) = vm_payload_config {
51                 vm_payload_config
52                     .apexes
53                     .iter()
54                     .map(|x| x.name.clone())
55                     .collect::<Vec<String>>()
56                     .join(":")
57             } else {
58                 "INFO: Can't get VmPayloadConfig".to_owned()
59             }
60         }
61     }
62 }
63 
get_vm_payload_config( apk_fd: &Option<ParcelFileDescriptor>, config_path: &str, ) -> Result<VmPayloadConfig>64 fn get_vm_payload_config(
65     apk_fd: &Option<ParcelFileDescriptor>,
66     config_path: &str,
67 ) -> Result<VmPayloadConfig> {
68     let apk = apk_fd.as_ref().ok_or_else(|| anyhow!("APK is none"))?;
69     let apk_file = clone_file(apk)?;
70     let mut apk_zip = ZipArchive::new(&apk_file)?;
71     let config_file = apk_zip.by_name(config_path)?;
72     let vm_payload_config: VmPayloadConfig = serde_json::from_reader(config_file)?;
73     Ok(vm_payload_config)
74 }
75 
get_duration(vm_start_timestamp: Option<SystemTime>) -> Duration76 fn get_duration(vm_start_timestamp: Option<SystemTime>) -> Duration {
77     match vm_start_timestamp {
78         Some(vm_start_timestamp) => vm_start_timestamp.elapsed().unwrap_or_default(),
79         None => Duration::default(),
80     }
81 }
82 
83 // Returns the number of CPUs configured in the host system.
84 // This matches how crosvm determines the number of logical cores.
85 // For telemetry purposes only.
get_num_cpus() -> Option<usize>86 pub(crate) fn get_num_cpus() -> Option<usize> {
87     // SAFETY: Only integer constants passed back and forth.
88     let ret = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) };
89     if ret > 0 {
90         ret.try_into().ok()
91     } else {
92         None
93     }
94 }
95 
get_num_vcpus(cpu_options: &CpuOptions) -> i3296 fn get_num_vcpus(cpu_options: &CpuOptions) -> i32 {
97     match cpu_options.cpuTopology {
98         CpuTopology::MatchHost(_) => {
99             get_num_cpus().and_then(|v| v.try_into().ok()).unwrap_or_else(|| {
100                 warn!("Failed to determine the number of CPUs in the host");
101                 INVALID_NUM_CPUS
102             })
103         }
104         CpuTopology::CpuCount(count) => count,
105     }
106 }
107 
108 /// Write the stats of VMCreation to statsd
109 /// 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>>, )110 pub fn write_vm_creation_stats(
111     config: &VirtualMachineConfig,
112     is_protected: bool,
113     ret: &binder::Result<Strong<dyn IVirtualMachine>>,
114 ) {
115     if cfg!(early) {
116         info!("Writing VmCreationRequested atom for early VMs is not implemented; skipping");
117         return;
118     }
119     let creation_succeeded;
120     let binder_exception_code;
121     match ret {
122         Ok(_) => {
123             creation_succeeded = true;
124             binder_exception_code = Status::ok().exception_code() as i32;
125         }
126         Err(ref e) => {
127             creation_succeeded = false;
128             binder_exception_code = e.exception_code() as i32;
129         }
130     }
131 
132     let (vm_identifier, config_type, num_cpus, memory_mib, apexes) = match config {
133         VirtualMachineConfig::AppConfig(config) => (
134             config.name.clone(),
135             vm_creation_requested::ConfigType::VirtualMachineAppConfig,
136             get_num_vcpus(&config.cpuOptions),
137             config.memoryMib,
138             get_apex_list(config),
139         ),
140         VirtualMachineConfig::RawConfig(config) => (
141             config.name.clone(),
142             vm_creation_requested::ConfigType::VirtualMachineRawConfig,
143             get_num_vcpus(&config.cpuOptions),
144             config.memoryMib,
145             String::new(),
146         ),
147     };
148 
149     let atom = AtomVmCreationRequested {
150         uid: get_calling_uid() as i32,
151         vmIdentifier: vm_identifier,
152         isProtected: is_protected,
153         creationSucceeded: creation_succeeded,
154         binderExceptionCode: binder_exception_code,
155         configType: config_type as i32,
156         numCpus: num_cpus,
157         memoryMib: memory_mib,
158         apexes,
159     };
160 
161     info!("Writing VmCreationRequested atom into statsd.");
162     thread::spawn(move || {
163         GLOBAL_SERVICE.atomVmCreationRequested(&atom).unwrap_or_else(|e| {
164             warn!("Failed to write VmCreationRequested atom: {e}");
165         });
166     });
167 }
168 
169 /// Write the stats of VM boot to statsd
170 /// 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>, )171 pub fn write_vm_booted_stats(
172     uid: i32,
173     vm_identifier: &str,
174     vm_start_timestamp: Option<SystemTime>,
175 ) {
176     if cfg!(early) {
177         info!("Writing VmCreationRequested atom for early VMs is not implemented; skipping");
178         return;
179     }
180 
181     let vm_identifier = vm_identifier.to_owned();
182     let duration = get_duration(vm_start_timestamp);
183 
184     let atom = AtomVmBooted {
185         uid,
186         vmIdentifier: vm_identifier,
187         elapsedTimeMillis: duration.as_millis() as i64,
188     };
189 
190     info!("Writing VmBooted atom into statsd.");
191     thread::spawn(move || {
192         GLOBAL_SERVICE.atomVmBooted(&atom).unwrap_or_else(|e| {
193             warn!("Failed to write VmBooted atom: {e}");
194         });
195     });
196 }
197 
198 /// 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, )199 pub fn write_vm_exited_stats_sync(
200     uid: i32,
201     vm_identifier: &str,
202     reason: DeathReason,
203     exit_signal: Option<i32>,
204     vm_metric: &VmMetric,
205 ) {
206     if cfg!(early) {
207         info!("Writing VmExited atom for early VMs is not implemented; skipping");
208         return;
209     }
210     let vm_identifier = vm_identifier.to_owned();
211     let elapsed_time_millis = get_duration(vm_metric.start_timestamp).as_millis() as i64;
212     let guest_time_millis = vm_metric.cpu_guest_time.unwrap_or_default();
213     let rss = vm_metric.rss.unwrap_or_default();
214 
215     let atom = AtomVmExited {
216         uid,
217         vmIdentifier: vm_identifier,
218         elapsedTimeMillis: elapsed_time_millis,
219         deathReason: reason,
220         guestTimeMillis: guest_time_millis,
221         rssVmKb: rss.vm,
222         rssCrosvmKb: rss.crosvm,
223         exitSignal: exit_signal.unwrap_or_default(),
224     };
225 
226     info!("Writing VmExited atom into statsd.");
227     GLOBAL_SERVICE.atomVmExited(&atom).unwrap_or_else(|e| {
228         warn!("Failed to write VmExited atom: {e}");
229     });
230 }
231