diff options
author | Yi Kong <yikong@google.com> | 2021-03-02 13:58:25 +0800 |
---|---|---|
committer | Yi Kong <yikong@google.com> | 2021-03-03 14:24:33 +0800 |
commit | e3aab14ec0bcbbae6ba6edddbe315e1e128dce5f (patch) | |
tree | 1f5970b315e4c38542d5baf1d663040d877a8f5d /profcollectd | |
parent | f8abfdbd271086cf276fcf738e123cf36d3eb2ca (diff) | |
download | extras-e3aab14ec0bcbbae6ba6edddbe315e1e128dce5f.tar.gz |
profcollectd: Full rewrite in Rust 🦀
Rewritten from the C++ version. No major feature difference except that
the report zip is not compressed (blocked by upstream bug) and now
stored in a different location (to prepare for upload queue support).
Test: Manual testing
Change-Id: I635c9c56a3870c3b96b31d46df3ef9175490925d
Diffstat (limited to 'profcollectd')
24 files changed, 621 insertions, 953 deletions
diff --git a/profcollectd/Android.bp b/profcollectd/Android.bp index cd0d8852..f1723006 100644 --- a/profcollectd/Android.bp +++ b/profcollectd/Android.bp @@ -36,7 +36,10 @@ rust_binary { srcs: ["profcollectctl.rs"], - rustlibs: ["libprofcollectd_rust"], + rustlibs: [ + "libanyhow", + "libprofcollectd", + ], } rust_binary { @@ -44,7 +47,10 @@ rust_binary { srcs: ["profcollectd.rs"], - rustlibs: ["libprofcollectd_rust"], + rustlibs: [ + "libanyhow", + "libprofcollectd", + ], init_rc: ["profcollectd.rc"], } diff --git a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl index 908123ab..9bbe4a5c 100644 --- a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl +++ b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl @@ -18,11 +18,10 @@ package com.android.server.profcollect; /** {@hide} */ interface IProfCollectd { - void ReadConfig(); - void ScheduleCollection(); - void TerminateCollection(); - void TraceOnce(@utf8InCpp String tag); - void ProcessProfile(); - void CreateProfileReport(); - @utf8InCpp String GetSupportedProvider(); + void schedule(); + void terminate(); + void trace_once(@utf8InCpp String tag); + void process(boolean blocking); + void report(); + @utf8InCpp String get_supported_provider(); } diff --git a/profcollectd/libprofcollectd/Android.bp b/profcollectd/libprofcollectd/Android.bp index 509ac5de..3c709a2f 100644 --- a/profcollectd/libprofcollectd/Android.bp +++ b/profcollectd/libprofcollectd/Android.bp @@ -34,76 +34,27 @@ aidl_interface { }, } -rust_bindgen { - name: "libprofcollectd_bindgen", - crate_name: "profcollectd_bindgen", - source_stem: "libprofcollectd_bindgen", - wrapper_src: "include/libprofcollectd.hpp", -} - rust_library { - name: "libprofcollectd_rust", + name: "libprofcollectd", stem: "liblibprofcollectd", crate_name: "libprofcollectd", srcs: ["lib.rs"], rustlibs: [ - "profcollectd_aidl_interface-rust", - "libbinder_rs", - "libprofcollectd_bindgen", + "profcollectd_aidl_interface-rust", // Move to rlib once b/179041242 is fixed. + "libandroid_logger", + "libanyhow", + "libbinder_rs", // Remove once b/179041241 is fixed. + "libchrono", + "liblazy_static", + "liblog_rust", + "libserde", // Remove once b/179041241 is fixed. + "libserde_json", + "libzip", ], - shared_libs: [ - "libprofcollectd", + rlibs: [ + "libprofcollect_libflags_rust", + "libprofcollect_libbase_rust", + "libsimpleperf_profcollect_rust", ], -} - -cc_defaults { - name: "libprofcollectd_defaults", - - // We are only doing this for C++20. Can be removed after it becomes default. - cpp_std: "experimental", - - tidy: true, - tidy_checks: [ - "-google-runtime-int", - "-google-explicit-constructor", - "-bugprone-unhandled-self-assignment", - "-bugprone-macro-parentheses", - ], - - cflags: [ - // jsoncpp uses volatile. - "-Wno-deprecated-volatile", - ], -} - -cc_library { - name: "libprofcollectd", - - defaults: ["libprofcollectd_defaults"], - - shared_libs: [ - "libbase", - "libbinder", - "libjsoncpp", - "liblog", - "libsimpleperf_profcollect", - "libutils", - "libziparchive", - "profcollectd_aidl_interface-cpp", - "server_configurable_flags", - ], - - static_libs: [ - "libc++fs", - ], - - srcs: [ - "binder_service.cpp", - "compress.cpp", - "config_utils.cpp", - "scheduler.cpp", - "simpleperf_etm_provider.cpp", - ], - - export_include_dirs: ["include"], + shared_libs: ["libsimpleperf_profcollect"], } diff --git a/profcollectd/libprofcollectd/binder_service.cpp b/profcollectd/libprofcollectd/binder_service.cpp deleted file mode 100644 index f1d24b96..00000000 --- a/profcollectd/libprofcollectd/binder_service.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#define LOG_TAG "profcollectd_binder" - -#include "binder_service.h" - -#include <android-base/logging.h> - -#include "config_utils.h" -#include "hwtrace_provider.h" -#include "libprofcollectd.hpp" -#include "scheduler.h" - -namespace android { -namespace profcollectd { - -using ::android::binder::Status; -using ::android::profcollectd::ProfcollectdBinder; -using ::com::android::server::profcollect::IProfCollectd; - -namespace { - -static constexpr const char* NOT_ENABLED_ERRMSG = - "profcollectd is not enabled through device config."; -static constexpr config_t CONFIG_ENABLED = {"enabled", "0"}; // Disabled by default. - -} // namespace - -void InitService(bool start) { - if (defaultServiceManager()->checkService(String16(ProfcollectdBinder::getServiceName()))) { - ALOGE("Another instance of profcollectd is already running"); - exit(EXIT_FAILURE); - } - - sp<ProcessState> proc(ProcessState::self()); - sp<IServiceManager> sm(defaultServiceManager()); - auto svc = sp<ProfcollectdBinder>(new ProfcollectdBinder()); - sm->addService(String16(ProfcollectdBinder::getServiceName()), svc); - if (start) { - svc->ScheduleCollection(); - } - ProcessState::self()->startThreadPool(); - IPCThreadState::self()->joinThreadPool(); -} - -Status ProfcollectdBinder::ForwardScheduler(const std::function<OptError()>& action) { - if (Scheduler == nullptr) { - return Status::fromExceptionCode(1, NOT_ENABLED_ERRMSG); - } - - auto errmsg = action(); - if (errmsg) { - LOG(ERROR) << errmsg.value(); - return Status::fromExceptionCode(1, errmsg.value().c_str()); - } - return Status::ok(); -} - -ProfcollectdBinder::ProfcollectdBinder() { - static bool enabled = getConfigFlagBool(CONFIG_ENABLED); - - if (enabled) { - ProfcollectdBinder::Scheduler = std::make_unique<ProfcollectdScheduler>(); - LOG(INFO) << "Binder service started"; - } else { - LOG(INFO) << NOT_ENABLED_ERRMSG; - } -} - -Status ProfcollectdBinder::ReadConfig() { - return ForwardScheduler([=]() { return Scheduler->ReadConfig(); }); -} - -Status ProfcollectdBinder::ScheduleCollection() { - return ForwardScheduler([=]() { return Scheduler->ScheduleCollection(); }); -} - -Status ProfcollectdBinder::TerminateCollection() { - return ForwardScheduler([=]() { return Scheduler->TerminateCollection(); }); -} - -Status ProfcollectdBinder::TraceOnce(const std::string& tag) { - return ForwardScheduler([=]() { return Scheduler->TraceOnce(tag); }); -} - -Status ProfcollectdBinder::ProcessProfile() { - return ForwardScheduler([=]() { return Scheduler->ProcessProfile(); }); -} - -Status ProfcollectdBinder::CreateProfileReport() { - return ForwardScheduler([=]() { return Scheduler->CreateProfileReport(); }); -} - -Status ProfcollectdBinder::GetSupportedProvider(std::string* provider) { - return ForwardScheduler([=]() { return Scheduler->GetSupportedProvider(*provider); }); -} - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/binder_service.h b/profcollectd/libprofcollectd/binder_service.h deleted file mode 100644 index 28c69958..00000000 --- a/profcollectd/libprofcollectd/binder_service.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#pragma once - -#include <binder/BinderService.h> -#include <binder/Status.h> - -#include "com/android/server/profcollect/BnProfCollectd.h" -#include "scheduler.h" - -namespace android { -namespace profcollectd { - -class ProfcollectdBinder : public BinderService<ProfcollectdBinder>, - public ::com::android::server::profcollect::BnProfCollectd { - public: - explicit ProfcollectdBinder(); - - static constexpr const char* getServiceName() { return "profcollectd"; } - - binder::Status ReadConfig() override; - binder::Status ScheduleCollection() override; - binder::Status TerminateCollection() override; - binder::Status TraceOnce(const std::string& tag) override; - binder::Status ProcessProfile() override; - binder::Status CreateProfileReport() override; - binder::Status GetSupportedProvider(std::string* provider) override; - - protected: - inline static std::unique_ptr<ProfcollectdScheduler> Scheduler; - - private: - binder::Status ForwardScheduler(const std::function<OptError()>& action); -}; - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/bindings/libflags/Android.bp b/profcollectd/libprofcollectd/bindings/libflags/Android.bp index f7cf2ed9..102a6c05 100644 --- a/profcollectd/libprofcollectd/bindings/libflags/Android.bp +++ b/profcollectd/libprofcollectd/bindings/libflags/Android.bp @@ -39,7 +39,7 @@ rust_library { name: "libprofcollect_libflags_rust", crate_name: "profcollect_libflags_rust", srcs: ["lib.rs"], - rustlibs: ["libprofcollect_libflags_bindgen"], + rlibs: ["libprofcollect_libflags_bindgen"], static_libs: ["libprofcollect_libflags"], shared_libs: [ "libc++", diff --git a/profcollectd/libprofcollectd/compress.cpp b/profcollectd/libprofcollectd/compress.cpp deleted file mode 100644 index 6a2b2e8e..00000000 --- a/profcollectd/libprofcollectd/compress.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#include "compress.h" - -#include <ziparchive/zip_writer.h> -#include <cstdlib> -#include <fstream> -#include <iostream> - -namespace android { -namespace profcollectd { - -bool CompressFiles(const std::filesystem::path& output, - std::vector<std::filesystem::path>& inputFiles) { - FILE* outputFile = std::fopen(output.c_str(), "wb"); - ZipWriter writer(outputFile); - - for (const auto& f : inputFiles) { - // read profile into memory. - std::ifstream ifs(f, std::ios::in | std::ios::binary); - std::vector<char> buf((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); - - // append to zip file - writer.StartEntry(f.filename().string(), ZipWriter::kCompress | ZipWriter::kAlign32); - writer.WriteBytes(buf.data(), buf.size()); - writer.FinishEntry(); - } - - auto ret = writer.Finish(); - std::fclose(outputFile); - return ret == 0; -} - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/compress.h b/profcollectd/libprofcollectd/compress.h deleted file mode 100644 index f99deca1..00000000 --- a/profcollectd/libprofcollectd/compress.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#pragma once - -#include <filesystem> - -namespace android { -namespace profcollectd { - -bool CompressFiles(const std::filesystem::path& output, std::vector<std::filesystem::path>& files); - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/config.rs b/profcollectd/libprofcollectd/config.rs new file mode 100644 index 00000000..e9a1d288 --- /dev/null +++ b/profcollectd/libprofcollectd/config.rs @@ -0,0 +1,95 @@ +// +// 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 configurations. + +use anyhow::Result; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::path::Path; +use std::str::FromStr; +use std::time::Duration; + +const PROFCOLLECT_CONFIG_NAMESPACE: &str = "profcollect_native_boot"; + +lazy_static! { + pub static ref TRACE_OUTPUT_DIR: &'static Path = Path::new("/data/misc/profcollectd/trace/"); + pub static ref PROFILE_OUTPUT_DIR: &'static Path = Path::new("/data/misc/profcollectd/output/"); + pub static ref REPORT_OUTPUT_DIR: &'static Path = Path::new("/data/misc/profcollectd/report/"); + pub static ref CONFIG_FILE: &'static Path = + Path::new("/data/misc/profcollectd/output/config.json"); +} + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct Config { + /// Version of config file scheme, always equals to 1. + version: u32, + /// Device build fingerprint. + pub build_fingerprint: String, + /// Interval between collections. + pub collection_interval: Duration, + /// Length of time each collection lasts for. + pub sampling_period: Duration, + /// An optional filter to limit which binaries to or not to profile. + pub binary_filter: String, +} + +impl Config { + pub fn from_env() -> Result<Self> { + Ok(Config { + version: 1, + build_fingerprint: get_build_fingerprint(), + collection_interval: Duration::from_secs(get_device_config( + "collection_interval", + 600, + )?), + sampling_period: Duration::from_millis(get_device_config("sampling_period", 500)?), + binary_filter: get_device_config("binary_filter", "".to_string())?, + }) + } +} + +impl ToString for Config { + fn to_string(&self) -> String { + serde_json::to_string(self).expect("Failed to deserialise configuration.") + } +} + +impl FromStr for Config { + type Err = serde_json::Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + serde_json::from_str::<Config>(s) + } +} + +fn get_build_fingerprint() -> String { + profcollect_libbase_rust::get_property("ro.build.fingerprint", "unknown").to_string() +} + +fn get_device_config<T>(key: &str, default: T) -> Result<T> +where + T: FromStr + ToString, + T::Err: Error + Send + Sync + 'static, +{ + let default = default.to_string(); + let config = profcollect_libflags_rust::get_server_configurable_flag( + &PROFCOLLECT_CONFIG_NAMESPACE, + &key, + &default, + ); + Ok(T::from_str(&config)?) +} diff --git a/profcollectd/libprofcollectd/config_utils.cpp b/profcollectd/libprofcollectd/config_utils.cpp deleted file mode 100644 index ce92017d..00000000 --- a/profcollectd/libprofcollectd/config_utils.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#include "config_utils.h" - -#include <android-base/parsedouble.h> -#include <android-base/parseint.h> -#include <android-base/properties.h> -#include <server_configurable_flags/get_flags.h> - -static const std::string PROFCOLLECT_CONFIG_NAMESPACE = "profcollect_native_boot"; - -namespace android { -namespace profcollectd { - -using ::android::base::GetProperty; -using ::android::base::ParseFloat; -using ::android::base::ParseInt; -using ::server_configurable_flags::GetServerConfigurableFlag; - -std::string getBuildFingerprint() { - return GetProperty("ro.build.fingerprint", "unknown"); -} - -std::string getConfigFlag(const config_t& config) { - return GetServerConfigurableFlag(PROFCOLLECT_CONFIG_NAMESPACE, config.name, config.defaultValue); -} - -int getConfigFlagInt(const config_t& config) { - std::string value = getConfigFlag(config); - int i; - if (!ParseInt(value, &i)) { - // Use default value if the server config value is malformed. - return ParseInt(config.defaultValue, &i); - } - return i; -} - -float getConfigFlagFloat(const config_t& config) { - std::string value = getConfigFlag(config); - float f; - if (!ParseFloat(value, &f)) { - // Use default value if the server config value is malformed. - return ParseFloat(config.defaultValue, &f); - } - return f; -} - -bool getConfigFlagBool(const config_t& config) { - std::string value = getConfigFlag(config); - return value == "true"; -} - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/config_utils.h b/profcollectd/libprofcollectd/config_utils.h deleted file mode 100644 index b3a458a0..00000000 --- a/profcollectd/libprofcollectd/config_utils.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#pragma once - -#include <variant> - -#include <android-base/parseint.h> -#include <server_configurable_flags/get_flags.h> - -namespace android { -namespace profcollectd { - -struct config_t { - const char* name; - const char* defaultValue; -}; - -std::string getBuildFingerprint(); -std::string getConfigFlag(const config_t& config); -int getConfigFlagInt(const config_t& config); -float getConfigFlagFloat(const config_t& config); -bool getConfigFlagBool(const config_t& config); - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/hwtrace_provider.h b/profcollectd/libprofcollectd/hwtrace_provider.h deleted file mode 100644 index 8e10745f..00000000 --- a/profcollectd/libprofcollectd/hwtrace_provider.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#pragma once - -#include <chrono> -#include <filesystem> -#include <functional> - -namespace android { -namespace profcollectd { - -class HwtraceProvider { - public: - virtual ~HwtraceProvider() = default; - - /** - * Get the name of the trace provider. - */ - virtual std::string GetName() = 0; - - /** - * Trace for the given length of time. - * - * @param period Length of time to trace in seconds. - * @return True if successful. - */ - virtual bool Trace(const std::filesystem::path& outputPath, const std::string& tag, - std::chrono::duration<float> samplingPeriod) = 0; - - /** - * Process the hardware trace to generate simpleperf intermediate profile. - */ - virtual bool Process(const std::filesystem::path& inputPath, - const std::filesystem::path& outputPath, - const std::string& binaryFilter) = 0; -}; - -} // namespace profcollectd -} // namespace android
\ No newline at end of file diff --git a/profcollectd/libprofcollectd/lib.rs b/profcollectd/libprofcollectd/lib.rs index b8aafb44..0cb4e95a 100644 --- a/profcollectd/libprofcollectd/lib.rs +++ b/profcollectd/libprofcollectd/lib.rs @@ -16,54 +16,84 @@ //! ProfCollect Binder client interface. -use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd; +mod config; +mod report; +mod scheduler; +mod service; +mod simpleperf_etm_trace_provider; +mod trace_provider; + +use crate::binder::Status; +use anyhow::{anyhow, Context, Error, Result}; +use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd::{ + self, BnProfCollectd, +}; use profcollectd_aidl_interface::binder; +use service::ProfcollectdBinderService; + +const PROFCOLLECTD_SERVICE_NAME: &str = "profcollectd"; /// Initialise profcollectd service. -/// * `start` - Immediately schedule collection after service is initialised. -pub fn init_service(start: bool) { - unsafe { - profcollectd_bindgen::android_profcollectd_InitService(start); +/// * `schedule_now` - Immediately schedule collection after service is initialised. +pub fn init_service(schedule_now: bool) -> Result<()> { + binder::ProcessState::start_thread_pool(); + + let profcollect_binder_service = ProfcollectdBinderService::new()?; + binder::add_service( + &PROFCOLLECTD_SERVICE_NAME, + BnProfCollectd::new_binder(profcollect_binder_service).as_binder(), + ) + .context("Failed to register service.")?; + + if schedule_now { + trace_once("boot")?; + schedule()?; } + + binder::ProcessState::join_thread_pool(); + Ok(()) } fn get_profcollectd_service() -> binder::Strong<dyn IProfCollectd::IProfCollectd> { - let service_name = "profcollectd"; - binder::get_interface(&service_name).expect("could not get profcollectd binder service") + binder::get_interface(&PROFCOLLECTD_SERVICE_NAME) + .expect("Could not get profcollectd binder service") +} + +// b/181225442 +fn binder_status_to_err(s: &Status) -> Error { + anyhow!(s.to_string()) } /// Schedule periodic profile collection. -pub fn schedule_collection() { - let service = get_profcollectd_service(); - service.ScheduleCollection().unwrap(); +pub fn schedule() -> Result<()> { + get_profcollectd_service().schedule().map_err(|e| binder_status_to_err(&e)) } /// Terminate periodic profile collection. -pub fn terminate_collection() { - let service = get_profcollectd_service(); - service.TerminateCollection().unwrap(); +pub fn terminate() -> Result<()> { + get_profcollectd_service().terminate().map_err(|e| binder_status_to_err(&e)) } /// Immediately schedule a one-off trace. -pub fn trace_once() { - let service = get_profcollectd_service(); - service.TraceOnce("manual").unwrap(); +pub fn trace_once(tag: &str) -> Result<()> { + get_profcollectd_service().trace_once(tag).map_err(|e| binder_status_to_err(&e)) } -/// Process the profiles. -pub fn process() { - let service = get_profcollectd_service(); - service.ProcessProfile().unwrap(); +/// Process traces. +pub fn process() -> Result<()> { + get_profcollectd_service().process(true).map_err(|e| binder_status_to_err(&e)) } -/// Create profile report. -pub fn create_profile_report() { - let service = get_profcollectd_service(); - service.CreateProfileReport().unwrap(); +/// Process traces and report profile. +pub fn report() -> Result<()> { + get_profcollectd_service().report().map_err(|e| binder_status_to_err(&e)) } -/// Read configs from environment variables. -pub fn read_config() { - let service = get_profcollectd_service(); - service.ReadConfig().unwrap(); +/// Inits logging for Android +pub fn init_logging() { + android_logger::init_once( + android_logger::Config::default() + .with_tag("profcollectd") + .with_min_level(log::Level::Error), + ); } diff --git a/profcollectd/libprofcollectd/report.rs b/profcollectd/libprofcollectd/report.rs new file mode 100644 index 00000000..b3c8c96a --- /dev/null +++ b/profcollectd/libprofcollectd/report.rs @@ -0,0 +1,56 @@ +// +// 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. +// + +//! Pack profiles into reports. + +use anyhow::{anyhow, Result}; +use std::fs::{read_dir, remove_file, File}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; +use zip::write::FileOptions; +use zip::ZipWriter; + +pub fn pack_report(profile: &Path, report: &Path) -> Result<()> { + // TODO: Allow multiple profiles to be queued for upload. + let mut report = PathBuf::from(report); + report.push("report.zip"); + + // Remove the current report file if exists. + remove_file(&report).ok(); + + let report = File::create(report)?; + let options = FileOptions::default(); + let mut zip = ZipWriter::new(report); + + read_dir(profile)? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|e| e.is_file()) + .try_for_each(|e| -> Result<()> { + let filename = e + .file_name() + .and_then(|f| f.to_str()) + .ok_or_else(|| anyhow!("Malformed profile path: {}", e.display()))?; + zip.start_file(filename, options)?; + let mut f = File::open(e)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + zip.write_all(&*buffer)?; + Ok(()) + })?; + zip.finish()?; + Ok(()) +} diff --git a/profcollectd/libprofcollectd/scheduler.cpp b/profcollectd/libprofcollectd/scheduler.cpp deleted file mode 100644 index 7777ee46..00000000 --- a/profcollectd/libprofcollectd/scheduler.cpp +++ /dev/null @@ -1,235 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#define LOG_TAG "profcollectd_scheduler" - -#include "scheduler.h" - -#include <fstream> -#include <vector> - -#include <android-base/logging.h> - -#include "compress.h" -#include "config_utils.h" -#include "hwtrace_provider.h" -#include "json/json.h" -#include "json/writer.h" - -namespace fs = std::filesystem; - -namespace android { -namespace profcollectd { - -// Default option values. -static constexpr config_t CONFIG_BUILD_FINGERPRINT = {"build_fingerprint", "unknown"}; -static constexpr config_t CONFIG_COLLECTION_INTERVAL_SEC = {"collection_interval", "600"}; -static constexpr config_t CONFIG_SAMPLING_PERIOD_SEC = {"sampling_period", "0.5"}; -static constexpr config_t CONFIG_BINARY_FILTER = {"binary_filter", ""}; - -static const fs::path OUT_ROOT_DIR("/data/misc/profcollectd"); -static const fs::path TRACE_DIR(OUT_ROOT_DIR / "trace"); -static const fs::path OUTPUT_DIR(OUT_ROOT_DIR / "output"); -static const fs::path REPORT_FILE(OUT_ROOT_DIR / "report.zip"); - -// Hwtrace provider registry -extern std::unique_ptr<HwtraceProvider> REGISTER_SIMPLEPERF_ETM_PROVIDER(); - -namespace { - -void ClearDir(const fs::path& path) { - if (fs::exists(path)) { - for (const auto& entry : fs::directory_iterator(path)) { - fs::remove_all(entry); - } - } -} - -bool ClearOnConfigChange(const ProfcollectdScheduler::Config& config) { - const fs::path configFile = OUTPUT_DIR / "config.json"; - ProfcollectdScheduler::Config oldConfig{}; - - // Read old config, if exists. - if (fs::exists(configFile)) { - std::ifstream ifs(configFile); - ifs >> oldConfig; - } - - if (oldConfig != config) { - LOG(INFO) << "Clearing profiles due to config change."; - ClearDir(TRACE_DIR); - ClearDir(OUTPUT_DIR); - - // Write new config. - std::ofstream ofs(configFile); - ofs << config; - return true; - } - return false; -} - -void PeriodicCollectionWorker(std::future<void> terminationSignal, ProfcollectdScheduler& scheduler, - std::chrono::seconds& interval) { - do { - scheduler.TraceOnce("periodic"); - } while ((terminationSignal.wait_for(interval)) == std::future_status::timeout); -} - -} // namespace - -ProfcollectdScheduler::ProfcollectdScheduler() { - ReadConfig(); - - // Load a registered hardware trace provider. - if ((hwtracer = REGISTER_SIMPLEPERF_ETM_PROVIDER())) { - LOG(INFO) << "ETM provider registered."; - } else { - LOG(ERROR) << "No hardware trace provider available."; - } -} - -OptError ProfcollectdScheduler::ReadConfig() { - if (workerThread != nullptr) { - static std::string errmsg = "Terminate the collection before refreshing config."; - return errmsg; - } - - const std::lock_guard<std::mutex> lock(mu); - - config.buildFingerprint = getBuildFingerprint(); - config.collectionInterval = - std::chrono::seconds(getConfigFlagInt(CONFIG_COLLECTION_INTERVAL_SEC)); - config.samplingPeriod = - std::chrono::duration<float>(getConfigFlagFloat(CONFIG_SAMPLING_PERIOD_SEC)); - config.binaryFilter = getConfigFlag(CONFIG_BINARY_FILTER); - ClearOnConfigChange(config); - - return std::nullopt; -} - -OptError ProfcollectdScheduler::ScheduleCollection() { - if (workerThread != nullptr) { - static std::string errmsg = "Collection is already scheduled."; - return errmsg; - } - - workerThread = - std::make_unique<std::thread>(PeriodicCollectionWorker, terminate.get_future(), - std::ref(*this), std::ref(config.collectionInterval)); - return std::nullopt; -} - -OptError ProfcollectdScheduler::TerminateCollection() { - if (workerThread == nullptr) { - static std::string errmsg = "Collection is not scheduled."; - return errmsg; - } - - terminate.set_value(); - workerThread->join(); - workerThread = nullptr; - terminate = std::promise<void>(); // Reset promise. - return std::nullopt; -} - -OptError ProfcollectdScheduler::TraceOnce(const std::string& tag) { - if (!hwtracer) { - return "No trace provider registered."; - } - - const std::lock_guard<std::mutex> lock(mu); - bool success = hwtracer->Trace(TRACE_DIR, tag, config.samplingPeriod); - if (!success) { - static std::string errmsg = "Trace failed"; - return errmsg; - } - return std::nullopt; -} - -OptError ProfcollectdScheduler::ProcessProfile() { - if (!hwtracer) { - return "No trace provider registered."; - } - - std::thread t([&]() { - const std::lock_guard<std::mutex> lock(mu); - hwtracer->Process(TRACE_DIR, OUTPUT_DIR, config.binaryFilter); - }); - t.detach(); - - return std::nullopt; -} - -OptError ProfcollectdScheduler::CreateProfileReport() { - if (!hwtracer) { - return "No trace provider registered."; - } - - std::thread t([&]() { - const std::lock_guard<std::mutex> lock(mu); - - hwtracer->Process(TRACE_DIR, OUTPUT_DIR, config.binaryFilter); - - std::vector<fs::path> profiles; - if (fs::exists(OUTPUT_DIR)) { - profiles.insert(profiles.begin(), fs::directory_iterator(OUTPUT_DIR), - fs::directory_iterator()); - } - CompressFiles(REPORT_FILE, profiles); - }); - t.detach(); - - return std::nullopt; -} - -OptError ProfcollectdScheduler::GetSupportedProvider(std::string& provider) { - provider = hwtracer ? hwtracer->GetName() : ""; - return std::nullopt; -} - -std::ostream& operator<<(std::ostream& os, const ProfcollectdScheduler::Config& config) { - Json::Value root; - root[CONFIG_BUILD_FINGERPRINT.name] = config.buildFingerprint; - root[CONFIG_COLLECTION_INTERVAL_SEC.name] = - static_cast<Json::Int64>(config.collectionInterval.count()); - root[CONFIG_SAMPLING_PERIOD_SEC.name] = config.samplingPeriod.count(); - root[CONFIG_BINARY_FILTER.name] = config.binaryFilter.c_str(); - Json::StreamWriterBuilder factory; - std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter()); - writer->write(root, &os); - return os; -} - -std::istream& operator>>(std::istream& is, ProfcollectdScheduler::Config& config) { - Json::Value root; - Json::CharReaderBuilder builder; - std::string errorMessage; - if (!Json::parseFromStream(builder, is, &root, &errorMessage)) { - return is; - } - - config.buildFingerprint = root[CONFIG_BUILD_FINGERPRINT.name].asString(); - config.collectionInterval = - std::chrono::seconds(root[CONFIG_COLLECTION_INTERVAL_SEC.name].asInt64()); - config.samplingPeriod = - std::chrono::duration<float>(root[CONFIG_SAMPLING_PERIOD_SEC.name].asFloat()); - config.binaryFilter = root[CONFIG_BINARY_FILTER.name].asString(); - - return is; -} - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/scheduler.h b/profcollectd/libprofcollectd/scheduler.h deleted file mode 100644 index b1ec07e2..00000000 --- a/profcollectd/libprofcollectd/scheduler.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#pragma once - -#include <compare> -#include <future> -#include <mutex> -#include <optional> -#include <thread> - -#include "hwtrace_provider.h" - -using OptError = std::optional<std::string>; - -namespace android { -namespace profcollectd { - -class ProfcollectdScheduler { - public: - struct Config { - std::string buildFingerprint; - std::chrono::seconds collectionInterval; - std::chrono::duration<float> samplingPeriod; - std::filesystem::path traceOutputDir; - std::filesystem::path profileOutputDir; - std::string binaryFilter; - - auto operator<=>(const Config&) const = default; - friend std::ostream& operator<<(std::ostream& os, const Config& config); - friend std::istream& operator>>(std::istream& is, Config& config); - }; - - explicit ProfcollectdScheduler(); - - // Methods below returns optional error message on failure, otherwise returns std::nullopt. - OptError ReadConfig(); - OptError ScheduleCollection(); - OptError TerminateCollection(); - OptError TraceOnce(const std::string& tag); - OptError ProcessProfile(); - OptError CreateProfileReport(); - OptError GetSupportedProvider(std::string& provider); - - private: - Config config; - std::promise<void> terminate; - std::unique_ptr<HwtraceProvider> hwtracer; - std::unique_ptr<std::thread> workerThread; - std::mutex mu; -}; - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/scheduler.rs b/profcollectd/libprofcollectd/scheduler.rs new file mode 100644 index 00000000..cfd0381f --- /dev/null +++ b/profcollectd/libprofcollectd/scheduler.rs @@ -0,0 +1,108 @@ +// +// 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 tracing scheduler. + +use std::sync::mpsc::{sync_channel, SyncSender}; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; + +use crate::config::{Config, PROFILE_OUTPUT_DIR, TRACE_OUTPUT_DIR}; +use crate::trace_provider::{self, TraceProvider}; +use anyhow::{anyhow, ensure, Context, Result}; + +pub struct Scheduler { + /// Signal to terminate the periodic collection worker thread, None if periodic collection is + /// not scheduled. + termination_ch: Option<SyncSender<()>>, + /// The preferred trace provider for the system. + trace_provider: Arc<Mutex<dyn TraceProvider + Send>>, +} + +impl Scheduler { + pub fn new() -> Result<Self> { + let p = trace_provider::get_trace_provider()?; + Ok(Scheduler { termination_ch: None, trace_provider: p }) + } + + fn is_scheduled(&self) -> bool { + self.termination_ch.is_some() + } + + pub fn schedule_periodic(&mut self, config: &Config) -> Result<()> { + ensure!(!self.is_scheduled(), "Already scheduled."); + + let (sender, receiver) = sync_channel(1); + self.termination_ch = Some(sender); + + // Clone config and trace_provider ARC for the worker thread. + let config = config.clone(); + let trace_provider = self.trace_provider.clone(); + + thread::spawn(move || { + loop { + match receiver.recv_timeout(config.collection_interval) { + Ok(_) => break, + Err(_) => { + // Did not receive a termination signal, initiate trace event. + trace_provider.lock().unwrap().trace( + &TRACE_OUTPUT_DIR, + "periodic", + &config.sampling_period, + ); + } + } + } + }); + Ok(()) + } + + pub fn terminate_periodic(&mut self) -> Result<()> { + self.termination_ch + .as_ref() + .ok_or_else(|| anyhow!("Not scheduled"))? + .send(()) + .context("Scheduler worker disappeared.")?; + self.termination_ch = None; + Ok(()) + } + + pub fn one_shot(&self, config: &Config, tag: &str) -> Result<()> { + let trace_provider = self.trace_provider.clone(); + trace_provider.lock().unwrap().trace(&TRACE_OUTPUT_DIR, tag, &config.sampling_period); + Ok(()) + } + + pub fn process(&self, blocking: bool) -> Result<()> { + let trace_provider = self.trace_provider.clone(); + let handle = thread::spawn(move || { + trace_provider + .lock() + .unwrap() + .process(&TRACE_OUTPUT_DIR, &PROFILE_OUTPUT_DIR) + .expect("Failed to process profiles."); + }); + if blocking { + handle.join().map_err(|_| anyhow!("Profile process thread panicked."))?; + } + Ok(()) + } + + pub fn get_trace_provider_name(&self) -> &'static str { + self.trace_provider.lock().unwrap().get_name() + } +} diff --git a/profcollectd/libprofcollectd/service.rs b/profcollectd/libprofcollectd/service.rs new file mode 100644 index 00000000..c01b8492 --- /dev/null +++ b/profcollectd/libprofcollectd/service.rs @@ -0,0 +1,118 @@ +// +// 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::{Context, Error, Result}; +use binder::public_api::Result as BinderResult; +use binder::Status; +use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd::IProfCollectd; +use std::ffi::CString; +use std::fs::{create_dir, read_to_string, remove_dir_all, write}; +use std::{ + str::FromStr, + sync::{Mutex, MutexGuard}, +}; + +use crate::config::{Config, CONFIG_FILE, PROFILE_OUTPUT_DIR, REPORT_OUTPUT_DIR, TRACE_OUTPUT_DIR}; +use crate::report::pack_report; +use crate::scheduler::Scheduler; + +fn err_to_binder_status(msg: Error) -> Status { + let msg = CString::new(msg.to_string()).expect("Failed to convert to CString"); + Status::new_service_specific_error(1, Some(&msg)) +} + +pub struct ProfcollectdBinderService { + lock: Mutex<Lock>, +} + +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, blocking: bool) -> BinderResult<()> { + let lock = &mut *self.lock(); + lock.scheduler + .process(blocking) + .context("Failed to process profiles.") + .map_err(err_to_binder_status) + } + fn report(&self) -> BinderResult<()> { + self.process(true)?; + pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR) + .context("Failed to create profile report.") + .map_err(err_to_binder_status) + } + fn get_supported_provider(&self) -> BinderResult<String> { + Ok(self.lock().scheduler.get_trace_provider_name().to_string()) + } +} + +impl ProfcollectdBinderService { + pub fn new() -> Result<Self> { + 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, clearing traces."); + remove_dir_all(*PROFILE_OUTPUT_DIR)?; + remove_dir_all(*TRACE_OUTPUT_DIR)?; + create_dir(*PROFILE_OUTPUT_DIR)?; + create_dir(*TRACE_OUTPUT_DIR)?; + write(*CONFIG_FILE, &new_config.to_string())?; + } + + Ok(ProfcollectdBinderService { + lock: Mutex::new(Lock { scheduler: new_scheduler, config: new_config }), + }) + } + + fn lock(&self) -> MutexGuard<Lock> { + self.lock.lock().unwrap() + } +} diff --git a/profcollectd/libprofcollectd/simpleperf_etm_provider.cpp b/profcollectd/libprofcollectd/simpleperf_etm_provider.cpp deleted file mode 100644 index 5e8c55bc..00000000 --- a/profcollectd/libprofcollectd/simpleperf_etm_provider.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (C) 2020 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. -// - -#define LOG_TAG "profcolelctd_simpleperf_etm" - -#include "hwtrace_provider.h" - -#include <android-base/logging.h> -#include <simpleperf_profcollect.h> - -#include <cstdlib> -#include <filesystem> -#include <sstream> -#include <string> -#include <thread> - -static constexpr const char* ETM_TRACEFILE_EXTENSION = ".etmtrace"; -static constexpr const char* OUTPUT_FILE_EXTENSION = ".data"; - -namespace { - -std::string GetTimestamp() { - auto now = std::time(nullptr); - char timestr[32]; - std::strftime(timestr, sizeof(timestr), "%Y%m%d-%H%M%S", std::localtime(&now)); - return timestr; -} - -} // namespace - -namespace android { -namespace profcollectd { - -namespace fs = std::filesystem; - -class SimpleperfETMProvider : public HwtraceProvider { - public: - static bool IsSupported(); - std::string GetName(); - bool Trace(const fs::path& outputPath, const std::string& tag, - std::chrono::duration<float> samplingPeriod) override; - bool Process(const fs::path& inputPath, const fs::path& outputPath, - const std::string& binaryFilter) override; -}; - -bool SimpleperfETMProvider::IsSupported() { - return simpleperf::etm::HasSupport(); -} - -std::string SimpleperfETMProvider::GetName() { - static constexpr const char* name = "simpleperf_etm"; - return name; -} - -bool SimpleperfETMProvider::Trace(const fs::path& outputPath, const std::string& tag, - std::chrono::duration<float> samplingPeriod) { - const std::string timestamp = GetTimestamp(); - auto outputFile = outputPath / (timestamp + "_" + tag + ETM_TRACEFILE_EXTENSION); - return simpleperf::etm::Record(outputFile, samplingPeriod); -} - -bool SimpleperfETMProvider::Process(const fs::path& inputPath, const fs::path& outputPath, - const std::string& binaryFilter) { - for (const auto& entry : fs::directory_iterator(inputPath)) { - const fs::path& traceFile = entry.path(); - if (traceFile.extension() != ETM_TRACEFILE_EXTENSION) { - continue; - } - - const fs::path binaryOutput = - outputPath / traceFile.filename().replace_extension(OUTPUT_FILE_EXTENSION); - - bool success = simpleperf::etm::Inject(traceFile, binaryOutput, binaryFilter); - if (success) { - fs::remove(traceFile); - } - } - - return true; -} - -std::unique_ptr<HwtraceProvider> REGISTER_SIMPLEPERF_ETM_PROVIDER() { - if (SimpleperfETMProvider::IsSupported()) { - return std::make_unique<SimpleperfETMProvider>(); - } - return nullptr; -} - -} // namespace profcollectd -} // namespace android diff --git a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs new file mode 100644 index 00000000..c4b9db76 --- /dev/null +++ b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs @@ -0,0 +1,75 @@ +// +// 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. +// + +//! Trace provider backed by ARM Coresight ETM, using simpleperf tool. + +use anyhow::{anyhow, Result}; +use std::fs::{read_dir, remove_file}; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use trace_provider::TraceProvider; + +use crate::trace_provider; + +static ETM_TRACEFILE_EXTENSION: &str = "etmtrace"; +static ETM_PROFILE_EXTENSION: &str = "data"; + +pub struct SimpleperfEtmTraceProvider {} + +impl TraceProvider for SimpleperfEtmTraceProvider { + fn get_name(&self) -> &'static str { + "simpleperf_etm" + } + + fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration) { + let mut trace_file = PathBuf::from(trace_dir); + trace_file.push(trace_provider::construct_file_name(tag)); + trace_file.set_extension(ETM_TRACEFILE_EXTENSION); + + simpleperf_profcollect::record(&trace_file, sampling_period) + } + + fn process(&self, trace_dir: &Path, profile_dir: &Path) -> Result<()> { + read_dir(trace_dir)? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|e| { + e.is_file() + && e.extension() + .and_then(|f| f.to_str()) + .filter(|ext| ext == &ETM_TRACEFILE_EXTENSION) + .is_some() + }) + .try_for_each(|trace_file| -> Result<()> { + let mut profile_file = PathBuf::from(profile_dir); + profile_file.push( + trace_file + .file_name() + .ok_or_else(|| anyhow!("Malformed trace path: {}", trace_file.display()))?, + ); + profile_file.set_extension(ETM_PROFILE_EXTENSION); + simpleperf_profcollect::process(&trace_file, &profile_file); + remove_file(&trace_file)?; + Ok(()) + }) + } +} + +impl SimpleperfEtmTraceProvider { + pub fn supported() -> bool { + simpleperf_profcollect::has_support() + } +} diff --git a/profcollectd/libprofcollectd/trace_provider.rs b/profcollectd/libprofcollectd/trace_provider.rs new file mode 100644 index 00000000..ed0d56f5 --- /dev/null +++ b/profcollectd/libprofcollectd/trace_provider.rs @@ -0,0 +1,44 @@ +// +// 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 trace provider trait and helper functions. + +use anyhow::{anyhow, Result}; +use chrono::Utc; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use crate::simpleperf_etm_trace_provider::SimpleperfEtmTraceProvider; + +pub trait TraceProvider { + fn get_name(&self) -> &'static str; + fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration); + fn process(&self, trace_dir: &Path, profile_dir: &Path) -> Result<()>; +} + +pub fn get_trace_provider() -> Result<Arc<Mutex<dyn TraceProvider + Send>>> { + if SimpleperfEtmTraceProvider::supported() { + log::info!("simpleperf_etm trace provider registered."); + return Ok(Arc::new(Mutex::new(SimpleperfEtmTraceProvider {}))); + } + + Err(anyhow!("No trace provider found for this device.")) +} + +pub fn construct_file_name(tag: &str) -> String { + format!("{}_{}", Utc::now().format("%Y%m%d-%H%M%S"), tag) +} diff --git a/profcollectd/profcollectctl.rs b/profcollectd/profcollectctl.rs index 4e75af44..97213172 100644 --- a/profcollectd/profcollectctl.rs +++ b/profcollectd/profcollectctl.rs @@ -16,11 +16,10 @@ //! Command to control profcollectd behaviour. +use anyhow::{bail, Context, Result}; use std::env; -fn print_help() { - println!( - r#"( +const HELP_MSG: &str = r#" usage: profcollectctl [command] Command to control profcollectd behaviour. @@ -33,47 +32,40 @@ command: report Create a report containing all profiles. reconfig Refresh configuration. help Print this message. -)"# - ); -} +"#; + +fn main() -> Result<()> { + libprofcollectd::init_logging(); -fn main() { let args: Vec<String> = env::args().collect(); if args.len() != 2 { - print_help(); - std::process::exit(1); + bail!("This program only takes one argument{}", &HELP_MSG); } let action = &args[1]; match action.as_str() { "start" => { println!("Scheduling profile collection"); - libprofcollectd::schedule_collection(); + libprofcollectd::schedule().context("Failed to schedule collection.")?; } "stop" => { println!("Terminating profile collection"); - libprofcollectd::terminate_collection(); + libprofcollectd::terminate().context("Failed to terminate collection.")?; } "once" => { println!("Trace once"); - libprofcollectd::trace_once(); + libprofcollectd::trace_once("manual").context("Failed to trace.")?; } "process" => { - println!("Processing traces in background"); - libprofcollectd::process(); + println!("Processing traces"); + libprofcollectd::process().context("Failed to process traces.")?; } "report" => { - println!("Creating profile report in background"); - libprofcollectd::create_profile_report(); - } - "reconfig" => { - println!("Refreshing configuration"); - libprofcollectd::read_config(); - } - "help" => print_help(), - _ => { - print_help(); - std::process::exit(1); + println!("Creating profile report"); + libprofcollectd::report().context("Failed to create profile report.")?; } + "help" => println!("{}", &HELP_MSG), + arg => bail!("Unknown argument: {}\n{}", &arg, &HELP_MSG), } + Ok(()) } diff --git a/profcollectd/profcollectd.rc b/profcollectd/profcollectd.rc index bc4db95a..37fc846e 100644 --- a/profcollectd/profcollectd.rc +++ b/profcollectd/profcollectd.rc @@ -1,4 +1,4 @@ -service profcollectd /system/bin/profcollectd boot +service profcollectd /system/bin/profcollectd class late_start disabled oneshot @@ -14,3 +14,4 @@ on post-fs-data mkdir /data/misc/profcollectd 0770 root root mkdir /data/misc/profcollectd/trace 0770 root root mkdir /data/misc/profcollectd/output 0770 root root + mkdir /data/misc/profcollectd/report 0770 root root diff --git a/profcollectd/profcollectd.rs b/profcollectd/profcollectd.rs index 67563bff..0c2e87d9 100644 --- a/profcollectd/profcollectd.rs +++ b/profcollectd/profcollectd.rs @@ -16,33 +16,31 @@ //! Daemon program to collect system traces. +use anyhow::{bail, Result}; use std::env; -fn print_help() { - println!( - r#"( +const HELP_MSG: &str = r#" +profcollectd background daemon. usage: profcollectd [command] - boot Start daemon and schedule profile collection after a short delay. - run Start daemon but do not schedule profile collection. -)"# - ); -} + nostart Start daemon but do not schedule profile collection. +"#; + +fn main() -> Result<()> { + libprofcollectd::init_logging(); -fn main() { let args: Vec<String> = env::args().collect(); - if args.len() != 2 { - print_help(); - std::process::exit(1); + if args.len() > 2 { + bail!("This program only takes one or no argument{}", &HELP_MSG); + } + if args.len() == 1 { + libprofcollectd::init_service(true)?; } let action = &args[1]; match action.as_str() { - "boot" => libprofcollectd::init_service(true), - "run" => libprofcollectd::init_service(false), - "help" => print_help(), - _ => { - print_help(); - std::process::exit(1); - } + "nostart" => libprofcollectd::init_service(false)?, + "help" => println!("{}", &HELP_MSG), + arg => bail!("Unknown argument: {}\n{}", &arg, &HELP_MSG), } + Ok(()) } |