// // Copyright (C) 2021 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. // //! ProfCollect Binder service implementation. use anyhow::{anyhow, Context, Error, Result}; use binder::Result as BinderResult; use binder::{SpIBinder, Status}; use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd::IProfCollectd; use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProviderStatusCallback::IProviderStatusCallback; use std::ffi::CString; use std::fs::{read_dir, read_to_string, remove_file, write}; use std::str::FromStr; use std::sync::{Mutex, MutexGuard}; use std::time::Duration; use crate::config::{ clear_data, Config, CONFIG_FILE, PROFILE_OUTPUT_DIR, REPORT_OUTPUT_DIR, REPORT_RETENTION_SECS, }; use crate::report::{get_report_ts, pack_report}; use crate::scheduler::Scheduler; pub fn err_to_binder_status(msg: Error) -> Status { let msg = format!("{:#?}", msg); let msg = CString::new(msg).expect("Failed to convert to CString"); Status::new_service_specific_error(1, Some(&msg)) } pub struct ProfcollectdBinderService { lock: Mutex, } struct Lock { config: Config, scheduler: Scheduler, } impl binder::Interface for ProfcollectdBinderService {} impl IProfCollectd for ProfcollectdBinderService { fn schedule(&self) -> BinderResult<()> { let lock = &mut *self.lock(); lock.scheduler .schedule_periodic(&lock.config) .context("Failed to schedule collection.") .map_err(err_to_binder_status) } fn terminate(&self) -> BinderResult<()> { self.lock() .scheduler .terminate_periodic() .context("Failed to terminate collection.") .map_err(err_to_binder_status) } fn trace_once(&self, tag: &str) -> BinderResult<()> { let lock = &mut *self.lock(); lock.scheduler .one_shot(&lock.config, tag) .context("Failed to initiate an one-off trace.") .map_err(err_to_binder_status) } fn process(&self) -> BinderResult<()> { let lock = &mut *self.lock(); lock.scheduler .process(&lock.config) .context("Failed to process profiles.") .map_err(err_to_binder_status) } fn report(&self) -> BinderResult { self.process()?; let lock = &mut *self.lock(); pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config) .context("Failed to create profile report.") .map_err(err_to_binder_status) } fn get_supported_provider(&self) -> BinderResult { Ok(self.lock().scheduler.get_trace_provider_name().to_string()) } fn registerProviderStatusCallback( &self, cb: &binder::Strong<(dyn IProviderStatusCallback)>, ) -> BinderResult<()> { if self.lock().scheduler.is_provider_ready() { if let Err(e) = cb.onProviderReady() { log::error!("Failed to call ProviderStatusCallback {:?}", e); } return Ok(()); } let cb_binder: SpIBinder = cb.as_binder(); self.lock().scheduler.register_provider_ready_callback(Box::new(move || { if let Ok(cb) = cb_binder.into_interface::() { if let Err(e) = cb.onProviderReady() { log::error!("Failed to call ProviderStatusCallback {:?}", e) } } else { log::error!("SpIBinder is not a IProviderStatusCallback."); } })); Ok(()) } } impl ProfcollectdBinderService { pub fn new() -> Result { let new_scheduler = Scheduler::new()?; let new_config = Config::from_env()?; let config_changed = read_to_string(*CONFIG_FILE) .ok() .and_then(|s| Config::from_str(&s).ok()) .filter(|c| new_config == *c) .is_none(); if config_changed { log::info!("Config change detected, resetting profcollect."); clear_data()?; write(*CONFIG_FILE, &new_config.to_string())?; } // Clear profile reports out of rentention period. for report in read_dir(*REPORT_OUTPUT_DIR)? { let report = report?.path(); let report_name = report .file_stem() .and_then(|f| f.to_str()) .ok_or_else(|| anyhow!("Malformed path {}", report.display()))?; let report_ts = get_report_ts(report_name); if let Err(e) = report_ts { log::error!( "Cannot decode creation timestamp for report {}, caused by {}, deleting", report_name, e ); remove_file(report)?; continue; } let report_age = report_ts.unwrap().elapsed()?; if report_age > Duration::from_secs(REPORT_RETENTION_SECS) { log::info!("Report {} past rentention period, deleting", report_name); remove_file(report)?; } } Ok(ProfcollectdBinderService { lock: Mutex::new(Lock { scheduler: new_scheduler, config: new_config }), }) } fn lock(&self) -> MutexGuard { self.lock.lock().unwrap() } }