Implement collecting VmStatus in microdroid_manager

Bug: 236253808
Test: Manually run statsd_testdrive 512 while running atest MicrodroidTestCase#testTelemetryPushedAtoms
Change-Id: I55ebb75f93943a3095f27c74fcef577a1d054865
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 4b06b3e..8741fb8 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -34,6 +34,7 @@
         "libonce_cell",
         "libopenssl",
         "libprotobuf",
+        "libregex",
         "librpcbinder_rs",
         "librustutils",
         "libscopeguard",
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 78f8bb4..f465d43 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -18,6 +18,7 @@
 mod instance;
 mod ioutil;
 mod payload;
+mod procutil;
 mod swap;
 mod vm_payload_service;
 
@@ -25,8 +26,12 @@
 use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
 use crate::vm_payload_service::register_vm_payload_service;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
-use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
-    VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
+    IVirtualMachineService::{
+        IVirtualMachineService, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT,
+    },
+    VirtualMachineCpuStatus::VirtualMachineCpuStatus,
+    VirtualMachineMemStatus::VirtualMachineMemStatus,
 };
 use anyhow::{anyhow, bail, ensure, Context, Error, Result};
 use apkverify::{get_public_key_der, verify, V4Signature};
@@ -36,9 +41,10 @@
 use itertools::sorted;
 use log::{error, info};
 use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
-use microdroid_payload_config::{Task, TaskType, VmPayloadConfig, OsConfig};
+use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
 use openssl::sha::Sha512;
 use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
+use procutil::{get_cpu_time, get_mem_info};
 use rand::Fill;
 use rpcbinder::get_vsock_rpc_interface;
 use rustutils::system_properties;
@@ -50,6 +56,7 @@
 use std::path::Path;
 use std::process::{Child, Command, Stdio};
 use std::str;
+use std::thread;
 use std::time::{Duration, SystemTime};
 use vsock::VsockStream;
 
@@ -89,6 +96,42 @@
     InvalidConfig(String),
 }
 
+fn send_vm_status() -> Result<()> {
+    let service = get_vms_rpc_binder()
+        .context("cannot connect to VirtualMachineService")
+        .map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
+
+    let one_second = Duration::from_millis(1000);
+    loop {
+        // Collect VM CPU time information and creating VmCpuStatus atom for metrics.
+        let cpu_time = get_cpu_time()?;
+        let vm_cpu_status = VirtualMachineCpuStatus {
+            cpu_time_user: cpu_time.user,
+            cpu_time_nice: cpu_time.nice,
+            cpu_time_sys: cpu_time.sys,
+            cpu_time_idle: cpu_time.idle,
+        };
+        service
+            .notifyCpuStatus(&vm_cpu_status)
+            .expect("Can't send information about VM CPU status");
+
+        // Collect VM memory information and creating VmMemStatus atom for metrics.
+        let mem_info = get_mem_info()?;
+        let vm_mem_status = VirtualMachineMemStatus {
+            mem_total: mem_info.total,
+            mem_free: mem_info.free,
+            mem_available: mem_info.available,
+            mem_buffer: mem_info.buffer,
+            mem_cached: mem_info.cached,
+        };
+        service
+            .notifyMemStatus(&vm_mem_status)
+            .expect("Can't send information about VM memory status");
+
+        thread::sleep(one_second);
+    }
+}
+
 fn translate_error(err: &Error) -> (ErrorCode, String) {
     if let Some(e) = err.downcast_ref::<MicrodroidError>() {
         match e {
@@ -177,6 +220,13 @@
     let service = get_vms_rpc_binder()
         .context("cannot connect to VirtualMachineService")
         .map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
+
+    thread::spawn(move || {
+        if let Err(e) = send_vm_status() {
+            error!("failed to get virtual machine status: {:?}", e);
+        }
+    });
+
     match try_run_payload(&service) {
         Ok(code) => {
             info!("notifying payload finished");
diff --git a/microdroid_manager/src/procutil.rs b/microdroid_manager/src/procutil.rs
new file mode 100644
index 0000000..b323ca9
--- /dev/null
+++ b/microdroid_manager/src/procutil.rs
@@ -0,0 +1,125 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use anyhow::{Context, Result};
+use libc::{sysconf, _SC_CLK_TCK};
+use regex::Regex;
+use std::fs::{self, File};
+use std::io::{BufRead, BufReader};
+
+const MILLIS_PER_SEC: i64 = 1000;
+
+pub struct CpuTime {
+    pub user: i64,
+    pub nice: i64,
+    pub sys: i64,
+    pub idle: i64,
+}
+
+pub struct MemInfo {
+    pub total: i64,
+    pub free: i64,
+    pub available: i64,
+    pub buffer: i64,
+    pub cached: i64,
+}
+
+// Get CPU time information from /proc/stat
+//
+// /proc/stat example(omitted):
+//   cpu  24790952 21104390 10771070 10480973587 1700955 0 410931 0 316532 0
+//   cpu0 169636 141307 61153 81785791 9605 0 183524 0 1345 0
+//   cpu1 182431 198327 68273 81431817 10445 0 32392 0 2616 0
+//   cpu2 183209 174917 68591 81933935 12239 0 10042 0 2415 0
+//   cpu3 183413 177758 69908 81927474 13354 0 5853 0 2491 0
+//   intr 7913477443 39 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+//   ctxt 10326710014
+//   btime 1664123605
+//   processes 9225712
+//   procs_running 1
+//   procs_blocked 0
+//   softirq 2683914305 14595298 304837101 1581 327291100 16397051 0 208857783 1024640365 787932 786506094
+//
+// expected output:
+//   user: 24790952
+//   nice: 21104390
+//   sys: 10771070
+//   idle: 10480973587
+pub fn get_cpu_time() -> Result<CpuTime> {
+    let re = Regex::new(r"^cpu\s+([\d]+)\s([\d]+)\s([\d]+)\s([\d]+)").unwrap();
+
+    let mut proc_stat = BufReader::new(File::open("/proc/stat")?);
+    let mut line = String::new();
+    proc_stat.read_line(&mut line)?;
+    let data_list = re.captures(&line).context("Failed to capture values")?;
+
+    let ticks_per_sec = unsafe { sysconf(_SC_CLK_TCK) } as i64;
+    let cpu_time = CpuTime {
+        user: data_list.get(1).unwrap().as_str().parse::<i64>()? * MILLIS_PER_SEC / ticks_per_sec,
+        nice: data_list.get(2).unwrap().as_str().parse::<i64>()? * MILLIS_PER_SEC / ticks_per_sec,
+        sys: data_list.get(3).unwrap().as_str().parse::<i64>()? * MILLIS_PER_SEC / ticks_per_sec,
+        idle: data_list.get(4).unwrap().as_str().parse::<i64>()? * MILLIS_PER_SEC / ticks_per_sec,
+    };
+    Ok(cpu_time)
+}
+
+// Get memory information from /proc/meminfo
+//
+// /proc/meminfo example(omitted):
+//   MemTotal:       263742736 kB
+//   MemFree:        37144204 kB
+//   MemAvailable:   249168700 kB
+//   Buffers:        10231296 kB
+//   Cached:         189502836 kB
+//   SwapCached:       113848 kB
+//   Active:         132266424 kB
+//   Inactive:       73587504 kB
+//   Active(anon):    1455240 kB
+//   Inactive(anon):  6993584 kB
+//   Active(file):   130811184 kB
+//   Inactive(file): 66593920 kB
+//   Unevictable:       56436 kB
+//   Mlocked:           56436 kB
+//   SwapTotal:      255123452 kB
+//   SwapFree:       254499068 kB
+//   Dirty:               596 kB
+//   Writeback:             0 kB
+//   AnonPages:       5295864 kB
+//   Mapped:          3512608 kB
+//
+// expected output:
+//   total: 263742736
+//   free: 37144204
+//   available: 249168700
+//   buffer: 10231296
+//   cached: 189502836
+pub fn get_mem_info() -> Result<MemInfo> {
+    let re = Regex::new(r"^.*?:\s+([0-9]+)\skB").unwrap();
+
+    let proc_mem_info = fs::read_to_string("/proc/meminfo")?;
+    let data_list: Vec<_> = proc_mem_info
+        .trim()
+        .splitn(6, '\n')
+        .map(|s| re.captures(s).context("Failed to capture values").ok()?.get(1))
+        .collect();
+
+    let mem_info = MemInfo {
+        total: data_list[0].unwrap().as_str().parse::<i64>()?,
+        free: data_list[1].unwrap().as_str().parse::<i64>()?,
+        available: data_list[2].unwrap().as_str().parse::<i64>()?,
+        buffer: data_list[3].unwrap().as_str().parse::<i64>()?,
+        cached: data_list[4].unwrap().as_str().parse::<i64>()?,
+    };
+    Ok(mem_info)
+}
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index e8c1724..4fa5fa0 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -16,6 +16,8 @@
 package android.system.virtualmachineservice;
 
 import android.system.virtualizationcommon.ErrorCode;
+import android.system.virtualmachineservice.VirtualMachineCpuStatus;
+import android.system.virtualmachineservice.VirtualMachineMemStatus;
 
 /** {@hide} */
 interface IVirtualMachineService {
@@ -56,4 +58,14 @@
      * Notifies that an error has occurred inside the VM..
      */
     void notifyError(ErrorCode errorCode, in String message);
+
+    /**
+     * Notifies the current CPU status of the VM.
+     */
+    void notifyCpuStatus(in VirtualMachineCpuStatus cpuStatus);
+
+    /**
+     * Notifies the current memory status of the VM.
+     */
+    void notifyMemStatus(in VirtualMachineMemStatus memStatus);
 }
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineCpuStatus.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineCpuStatus.aidl
new file mode 100644
index 0000000..307c3f9
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineCpuStatus.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.virtualmachineservice;
+
+parcelable VirtualMachineCpuStatus {
+    long cpu_time_user;
+    long cpu_time_nice;
+    long cpu_time_sys;
+    long cpu_time_idle;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineMemStatus.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineMemStatus.aidl
new file mode 100644
index 0000000..3de57c6
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineMemStatus.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.virtualmachineservice;
+
+parcelable VirtualMachineMemStatus {
+    long mem_total;
+    long mem_free;
+    long mem_available;
+    long mem_buffer;
+    long mem_cached;
+}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index f956062..33102eb 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -14,13 +14,17 @@
 
 //! Implementation of the AIDL interface of the VirtualizationService.
 
-use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
+use crate::atom::{
+    write_vm_booted_stats, write_vm_cpu_status_stats, write_vm_creation_stats,
+    write_vm_mem_status_stats,
+};
 use crate::composite::make_composite_image;
 use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmInstance, VmState};
 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
+use crate::selinux::{getfilecon, SeContext};
 use crate::{Cid, FIRST_GUEST_CID, SYSPROP_LAST_CID};
-use crate::selinux::{SeContext, getfilecon};
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     DeathReason::DeathReason,
     DiskImage::DiskImage,
@@ -29,39 +33,42 @@
     IVirtualizationService::IVirtualizationService,
     Partition::Partition,
     PartitionType::PartitionType,
-    VirtualMachineAppConfig::{VirtualMachineAppConfig, Payload::Payload},
+    VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
     VirtualMachineConfig::VirtualMachineConfig,
     VirtualMachineDebugInfo::VirtualMachineDebugInfo,
     VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
     VirtualMachineRawConfig::VirtualMachineRawConfig,
     VirtualMachineState::VirtualMachineState,
 };
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
+    IVirtualMachineService::{
+        BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
+        VM_STREAM_SERVICE_PORT, VM_TOMBSTONES_SERVICE_PORT,
+    },
+    VirtualMachineCpuStatus::VirtualMachineCpuStatus,
+    VirtualMachineMemStatus::VirtualMachineMemStatus,
+};
+use anyhow::{anyhow, bail, Context, Result};
+use apkverify::{HashAlgorithm, V4Signature};
 use binder::{
     self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, ParcelFileDescriptor,
     SpIBinder, Status, StatusCode, Strong, ThreadState,
 };
-use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
-        BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
-        VM_STREAM_SERVICE_PORT, VM_TOMBSTONES_SERVICE_PORT,
-};
-use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
-use anyhow::{anyhow, bail, Context, Result};
-use rpcbinder::run_rpc_server_with_factory;
 use disk::QcowFile;
-use apkverify::{HashAlgorithm, V4Signature};
 use log::{debug, error, info, warn};
-use microdroid_payload_config::{VmPayloadConfig, OsConfig, Task, TaskType};
+use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
+use rpcbinder::run_rpc_server_with_factory;
 use rustutils::system_properties;
 use semver::VersionReq;
 use std::convert::TryInto;
 use std::ffi::CStr;
 use std::fs::{create_dir, File, OpenOptions};
-use std::io::{Error, ErrorKind, Write, Read};
+use std::io::{Error, ErrorKind, Read, Write};
 use std::num::NonZeroU32;
 use std::os::unix::io::{FromRawFd, IntoRawFd};
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex, Weak};
-use tombstoned_client::{TombstonedConnection, DebuggerdDumpType};
+use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
 use vmconfig::VmConfig;
 use vsock::{VsockListener, VsockStream};
 use zip::ZipArchive;
@@ -1168,6 +1175,36 @@
             ))
         }
     }
+
+    fn notifyCpuStatus(&self, status: &VirtualMachineCpuStatus) -> binder::Result<()> {
+        let cid = self.cid;
+        if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
+            info!("VM having CID {} encountered an error", cid);
+            write_vm_cpu_status_stats(vm.requester_uid as i32, &vm.name, status);
+            Ok(())
+        } else {
+            error!("notifyCurrentStatus is called from an unknown CID {}", cid);
+            Err(Status::new_service_specific_error_str(
+                -1,
+                Some(format!("cannot find a VM with CID {}", cid)),
+            ))
+        }
+    }
+
+    fn notifyMemStatus(&self, status: &VirtualMachineMemStatus) -> binder::Result<()> {
+        let cid = self.cid;
+        if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
+            info!("VM having CID {} encountered an error", cid);
+            write_vm_mem_status_stats(vm.requester_uid as i32, &vm.name, status);
+            Ok(())
+        } else {
+            error!("notifyCurrentStatus is called from an unknown CID {}", cid);
+            Err(Status::new_service_specific_error_str(
+                -1,
+                Some(format!("cannot find a VM with CID {}", cid)),
+            ))
+        }
+    }
 }
 
 impl VirtualMachineService {
diff --git a/virtualizationservice/src/atom.rs b/virtualizationservice/src/atom.rs
index eabb4cc..8c46ac5 100644
--- a/virtualizationservice/src/atom.rs
+++ b/virtualizationservice/src/atom.rs
@@ -22,11 +22,17 @@
     VirtualMachineConfig::VirtualMachineConfig,
 };
 use android_system_virtualizationservice::binder::{Status, Strong};
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
+    VirtualMachineCpuStatus::VirtualMachineCpuStatus,
+    VirtualMachineMemStatus::VirtualMachineMemStatus,
+};
 use anyhow::{anyhow, Result};
 use binder::{ParcelFileDescriptor, ThreadState};
 use log::{trace, warn};
 use microdroid_payload_config::VmPayloadConfig;
-use statslog_virtualization_rust::{vm_booted, vm_creation_requested, vm_exited};
+use statslog_virtualization_rust::{
+    vm_booted, vm_cpu_status_reported, vm_creation_requested, vm_exited, vm_mem_status_reported,
+};
 use std::time::{Duration, SystemTime};
 use zip::ZipArchive;
 
@@ -206,3 +212,48 @@
         Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
     }
 }
+
+/// Write the stats of VM cpu status to statsd
+pub fn write_vm_cpu_status_stats(
+    uid: i32,
+    vm_identifier: &String,
+    cpu_status: &VirtualMachineCpuStatus,
+) {
+    let vm_cpu_status_reported = vm_cpu_status_reported::VmCpuStatusReported {
+        uid,
+        vm_identifier,
+        cpu_time_user_millis: cpu_status.cpu_time_user,
+        cpu_time_nice_millis: cpu_status.cpu_time_nice,
+        cpu_time_sys_millis: cpu_status.cpu_time_sys,
+        cpu_time_idle_millis: cpu_status.cpu_time_idle,
+    };
+    match vm_cpu_status_reported.stats_write() {
+        Err(e) => {
+            warn!("statslog_rust failed with error: {}", e);
+        }
+        Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+    }
+}
+
+/// Write the stats of VM memory status to statsd
+pub fn write_vm_mem_status_stats(
+    uid: i32,
+    vm_identifier: &String,
+    mem_status: &VirtualMachineMemStatus,
+) {
+    let vm_mem_status_reported = vm_mem_status_reported::VmMemStatusReported {
+        uid,
+        vm_identifier,
+        mem_total_kb: mem_status.mem_total,
+        mem_free_kb: mem_status.mem_free,
+        mem_available_kb: mem_status.mem_available,
+        mem_buffer_kb: mem_status.mem_buffer,
+        mem_cached_kb: mem_status.mem_cached,
+    };
+    match vm_mem_status_reported.stats_write() {
+        Err(e) => {
+            warn!("statslog_rust failed with error: {}", e);
+        }
+        Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+    }
+}