summaryrefslogtreecommitdiff
path: root/src/perfetto/perfetto_consumer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/perfetto/perfetto_consumer.cc')
-rw-r--r--src/perfetto/perfetto_consumer.cc608
1 files changed, 0 insertions, 608 deletions
diff --git a/src/perfetto/perfetto_consumer.cc b/src/perfetto/perfetto_consumer.cc
deleted file mode 100644
index 4fe677b..0000000
--- a/src/perfetto/perfetto_consumer.cc
+++ /dev/null
@@ -1,608 +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 "perfetto/perfetto_consumer.h"
-
-#include "common/trace.h"
-
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <utils/Looper.h>
-#include <utils/Printer.h>
-
-#include <limits>
-#include <map>
-#include <memory>
-#include <mutex>
-#include <sstream>
-#include <vector>
-
-#include <inttypes.h>
-#include <time.h>
-
-namespace iorap::perfetto {
-
-using State = PerfettoConsumer::State;
-using Handle = PerfettoConsumer::Handle;
-static constexpr Handle kInvalidHandle = PerfettoConsumer::kInvalidHandle;
-using OnStateChangedCb = PerfettoConsumer::OnStateChangedCb;
-using TraceBuffer = PerfettoConsumer::TraceBuffer;
-
-enum class StateKind {
- kUncreated,
- kCreated,
- kStartedTracing,
- kReadTracing,
- kTimedOutDestroyed, // same as kDestroyed but timed out.
- kDestroyed, // calling kDestroyed before timing out.
-};
-
-std::ostream& operator<<(std::ostream& os, StateKind kind) {
- switch (kind) {
- case StateKind::kUncreated:
- os << "kUncreated";
- break;
- case StateKind::kCreated:
- os << "kCreated";
- break;
- case StateKind::kStartedTracing:
- os << "kStartedTracing";
- break;
- case StateKind::kReadTracing:
- os << "kReadTracing";
- break;
- case StateKind::kTimedOutDestroyed:
- os << "kTimedOutDestroyed";
- break;
- case StateKind::kDestroyed:
- os << "kDestroyed";
- break;
- default:
- os << "(invalid)";
- break;
- }
- return os;
-}
-
-std::string ToString(StateKind kind) {
- std::stringstream ss;
- ss << kind;
- return ss.str();
-}
-
-static constexpr uint64_t kSecToNano = 1000000000LL;
-
-static uint64_t GetTimeNanoseconds() {
- struct timespec now;
- clock_gettime(CLOCK_REALTIME, &now);
-
- uint64_t now_ns = (now.tv_sec * kSecToNano + now.tv_nsec);
- return now_ns;
-}
-
-// Describe the state of our handle in detail for debugging/logging.
-struct HandleDescription {
- Handle handle_;
- StateKind kind_{StateKind::kUncreated}; // Our state. required for correctness.
- OnStateChangedCb callback_{nullptr}; // Required for Destroy callbacks.
- void* callback_arg_{nullptr};
-
- // For dumping to logs:
- State state_{State::kSessionNotFound}; // perfetto state
- std::optional<uint64_t> started_tracing_ns_; // when StartedTracing last called.
- std::optional<uint64_t> read_trace_ns_; // When ReadTrace last called.
- std::uint64_t last_transition_ns_{0};
- std::optional<uint64_t> trace_cookie_; // atrace beginning at StartTracing.
- bool trace_ended_{false}; // atrace ending at ReadTrace or Destroy.
-
- HandleDescription(Handle handle): handle_(handle) {}
-};
-
-// pimpl idiom to hide the implementation details from header
-//
-// Track and verify that our perfetto usage is sane.
-struct PerfettoConsumerImpl::Impl {
- Impl() : raw_{new PerfettoConsumerRawImpl{}},
- message_handler_{new TraceMessageHandler{this}} {
- std::thread watchdog_thread{ [this]() {
- ::android::sp<::android::Looper> looper;
- {
- std::lock_guard<std::mutex> guard{looper_mutex_};
- looper = ::android::Looper::prepare(/*opts*/0);
- looper_ = looper;
- }
-
- static constexpr int kTimeoutMillis = std::numeric_limits<int>::max();
-
- while (true) {
- // Execute any pending callbacks, otherwise just block forever.
- int result = looper->pollAll(kTimeoutMillis);
-
- if (result == ::android::Looper::POLL_ERROR) {
- LOG(ERROR) << "PerfettoConsumerImpl::Looper got a POLL_ERROR";
- } else {
- LOG(DEBUG) << "PerfettoConsumerImpl::Looper result was " << result;
- }
- }
- }};
-
- // Let thread run freely on its own.
- watchdog_thread.detach();
-
- // Block until looper_ is prepared.
- while (true) {
- std::lock_guard<std::mutex> guard{looper_mutex_};
- if (looper_ != nullptr) {
- break;
- }
- }
- }
-
- private:
- std::unique_ptr<PerfettoConsumerRawImpl> raw_;
- std::map<Handle, HandleDescription> states_;
-
- // We need this to be a counter to avoid memory leaks.
- Handle last_created_{0};
- Handle last_destroyed_{0};
- uint64_t trace_cookie_{0};
-
- std::mutex mutex_; // Guard above values.
-
- ::android::sp<::android::Looper> looper_;
- std::mutex looper_mutex_; // Guard looper_.
-
- struct TraceMessageHandler : public ::android::MessageHandler {
- TraceMessageHandler(Impl* impl) : impl_{impl} {
- CHECK(impl != nullptr);
- }
-
- Impl* impl_;
-
- virtual void handleMessage(const ::android::Message& message) override {
- impl_->OnTraceMessage(static_cast<Handle>(message.what));
- }
- };
-
- ::android::sp<TraceMessageHandler> message_handler_;
-
- public:
- Handle Create(const void* config_proto,
- size_t config_len,
- OnStateChangedCb callback,
- void* callback_arg) {
- LOG(VERBOSE) << "PerfettoConsumer::Create("
- << "config_len=" << config_len << ")";
- Handle handle = raw_->Create(config_proto, config_len, callback, callback_arg);
-
- std::lock_guard<std::mutex> guard{mutex_};
-
- // Assume every Handle starts at 0 and then increments by 1 every Create.
- ++last_created_;
- CHECK_EQ(last_created_, handle) << "perfetto handle had unexpected behavior.";
- // Without this^ increment-by-1 behavior our detection of untracked state values is broken.
- // If we have to, we can go with Untracked=Uncreated|Destroyed but it's better to distinguish
- // the two if possible.
-
- HandleDescription handle_desc{handle};
- handle_desc.handle_ = handle;
- handle_desc.callback_ = callback;
- handle_desc.callback_arg_ = callback_arg;
- UpdateHandleDescription(/*inout*/&handle_desc, StateKind::kCreated);
-
- // assume we never wrap around due to using int64
- bool inserted = states_.insert({handle, handle_desc}).second;
- CHECK(inserted) << "perfetto handle was re-used: " << handle;
-
- return handle;
- }
-
- void StartTracing(Handle handle) {
- LOG(DEBUG) << "PerfettoConsumer::StartTracing(handle=" << handle << ")";
-
- uint64_t trace_cookie;
- {
- std::lock_guard<std::mutex> guard{mutex_};
-
- auto it = states_.find(handle);
- if (it == states_.end()) {
- LOG(ERROR) << "Cannot StartTracing(" << handle << "), untracked handle";
- return;
- }
- HandleDescription& handle_desc = it->second;
-
- raw_->StartTracing(handle);
- UpdateHandleDescription(/*inout*/&handle_desc, StateKind::kStartedTracing);
- }
-
- // Use a looper here to add a timeout and immediately destroy the trace buffer.
- CHECK_LE(static_cast<int64_t>(handle), static_cast<int64_t>(std::numeric_limits<int>::max()));
- int message_code = static_cast<int>(handle);
- ::android::Message message{message_code};
-
- std::lock_guard<std::mutex> looper_guard{looper_mutex_};
- looper_->sendMessageDelayed(static_cast<nsecs_t>(GetPropertyTraceTimeoutNs()),
- message_handler_,
- message);
- }
-
- TraceBuffer ReadTrace(Handle handle) {
- LOG(DEBUG) << "PerfettoConsumer::ReadTrace(handle=" << handle << ")";
-
- std::lock_guard<std::mutex> guard{mutex_};
-
- auto it = states_.find(handle);
- if (it == states_.end()) {
- LOG(ERROR) << "Cannot ReadTrace(" << handle << "), untracked handle";
- return TraceBuffer{};
- }
-
- HandleDescription& handle_desc = it->second;
-
- TraceBuffer trace_buffer = raw_->ReadTrace(handle);
- UpdateHandleDescription(/*inout*/&handle_desc, StateKind::kReadTracing);
-
- return trace_buffer;
- }
-
- void Destroy(Handle handle) {
- HandleDescription handle_desc{handle};
- TryDestroy(handle, /*do_destroy*/true, /*out*/&handle_desc);;
- }
-
- bool TryDestroy(Handle handle, bool do_destroy, /*out*/HandleDescription* handle_desc_out) {
- CHECK(handle_desc_out != nullptr);
-
- LOG(VERBOSE) << "PerfettoConsumer::Destroy(handle=" << handle << ")";
-
- std::lock_guard<std::mutex> guard{mutex_};
-
- auto it = states_.find(handle);
- if (it == states_.end()) {
- // Leniency for calling Destroy multiple times. It's not a mistake.
- LOG(ERROR) << "Cannot Destroy(" << handle << "), untracked handle";
- return false;
- }
-
- HandleDescription& handle_desc = it->second;
-
- if (do_destroy) {
- raw_->Destroy(handle);
- }
- UpdateHandleDescription(/*inout*/&handle_desc, StateKind::kDestroyed);
-
- *handle_desc_out = handle_desc;
-
- // No longer track this handle to avoid memory leaks.
- last_destroyed_ = handle;
- states_.erase(it);
-
- return true;
- }
-
- State PollState(Handle handle) {
- // Just pass-through the call, we never use it directly anyway.
- return raw_->PollState(handle);
- }
-
- // Either fetch or infer the current handle state from a handle.
- // Meant for debugging/logging only.
- HandleDescription GetOrInferHandleDescription(Handle handle) {
- std::lock_guard<std::mutex> guard{mutex_};
-
- auto it = states_.find(handle);
- if (it == states_.end()) {
- HandleDescription state{handle};
- // If it's untracked it hasn't been created yet, or it was already destroyed.
- if (IsDestroyed(handle)) {
- UpdateHandleDescription(/*inout*/&state, StateKind::kDestroyed);
- } else {
- if (!IsUncreated(handle)) {
- LOG(WARNING) << "bad state detection";
- }
- UpdateHandleDescription(/*inout*/&state, StateKind::kUncreated);
- }
- return state;
- }
- return it->second;
- }
-
- void OnTraceMessage(Handle handle) {
- LOG(VERBOSE) << "OnTraceMessage(" << static_cast<int64_t>(handle) << ")";
- HandleDescription handle_desc{handle};
- {
- std::lock_guard<std::mutex> guard{mutex_};
-
- auto it = states_.find(handle);
- if (it == states_.end()) {
- // Handle values are never re-used, so we can simply ignore the message here
- // instead of having to remove it from the message queue.
- LOG(VERBOSE) << "OnTraceMessage(" << static_cast<int64_t>(handle)
- << ") no longer tracked handle";
- return;
- }
- handle_desc = it->second;
- }
-
- // First check. Has this trace been active for too long?
- uint64_t now_ns = GetTimeNanoseconds();
- if (handle_desc.kind_ == StateKind::kStartedTracing) {
- // Ignore other kinds of traces because they don't exhaust perfetto resources.
- CHECK(handle_desc.started_tracing_ns_.has_value()) << static_cast<int64_t>(handle);
-
- uint64_t started_tracing_ns = *handle_desc.started_tracing_ns_;
-
- if ((now_ns - started_tracing_ns) > GetPropertyTraceTimeoutNs()) {
- LOG(WARNING) << "Perfetto Handle timed out after " << (now_ns - started_tracing_ns) << "ns"
- << ", forcibly destroying";
-
- // Let the callback handler call Destroy.
- handle_desc.callback_(handle, State::kTraceFailed, handle_desc.callback_arg_);
- }
- }
-
- // Second check. Are there too many traces now? Cull the old traces.
- std::vector<HandleDescription> handle_list;
- do {
- std::lock_guard<std::mutex> guard{mutex_};
-
- size_t max_trace_count = GetPropertyMaxTraceCount();
- if (states_.size() > max_trace_count) {
- size_t overflow_count = states_.size() - max_trace_count;
- LOG(WARNING) << "Too many perfetto handles, overflowed by " << overflow_count
- << ", pruning down to " << max_trace_count;
- } else {
- break;
- }
-
- size_t prune_count = states_.size() - max_trace_count;
- auto it = states_.begin();
- for (size_t i = 0; i < prune_count; ++i) {
- // Simply prune by handle 1,2,3,4...
- // We could do better with a timestamp if we wanted to.
- ++it;
- handle_list.push_back(it->second);
- }
- } while (false);
-
- for (HandleDescription& handle_desc : handle_list) {
- LOG(DEBUG) << "Perfetto handle pruned: " << static_cast<int64_t>(handle);
-
- // Let the callback handler call Destroy.
- handle_desc.callback_(handle, State::kTraceFailed, handle_desc.callback_arg_);
- }
- }
-
- private:
- static uint64_t GetPropertyTraceTimeoutNs() {
- static uint64_t value = // property is timeout in seconds
- ::android::base::GetUintProperty<uint64_t>("iorapd.perfetto.timeout", /*default*/10);
- return value * kSecToNano;
- }
-
- static size_t GetPropertyMaxTraceCount() {
- static size_t value =
- ::android::base::GetUintProperty<size_t>("iorapd.perfetto.max_traces", /*default*/5);
- return value;
- }
-
- void UpdateHandleDescription(/*inout*/HandleDescription* handle_desc, StateKind kind) {
- CHECK(handle_desc != nullptr);
- handle_desc->kind_ = kind;
- handle_desc->state_ = raw_->PollState(handle_desc->handle_);
-
- handle_desc->last_transition_ns_ = GetTimeNanoseconds();
- if (kind == StateKind::kStartedTracing) {
- if (!handle_desc->started_tracing_ns_) {
- handle_desc->started_tracing_ns_ = handle_desc->last_transition_ns_;
-
- handle_desc->trace_cookie_ = ++trace_cookie_;
-
- atrace_async_begin(ATRACE_TAG_ACTIVITY_MANAGER,
- "Perfetto Scoped Trace",
- *handle_desc->trace_cookie_);
- atrace_int(ATRACE_TAG_ACTIVITY_MANAGER,
- "Perfetto::Trace Handle",
- static_cast<int32_t>(handle_desc->handle_));
- }
- }
-
- if (kind == StateKind::kReadTracing) {
- if (!handle_desc->read_trace_ns_) {
- handle_desc->read_trace_ns_ = handle_desc->last_transition_ns_;
-
- if (handle_desc->trace_cookie_.has_value() && !handle_desc->trace_ended_) {
- atrace_async_end(ATRACE_TAG_ACTIVITY_MANAGER,
- "Perfetto Scoped Trace",
- handle_desc->trace_cookie_.value());
-
- handle_desc->trace_ended_ = true;
- }
- }
- }
-
- // If Destroy is called prior to ReadTrace, mark the atrace as finished.
- if (kind == StateKind::kDestroyed && handle_desc->trace_cookie_ && !handle_desc->trace_ended_) {
- atrace_async_end(ATRACE_TAG_ACTIVITY_MANAGER,
- "Perfetto Scoped Trace",
- *handle_desc->trace_cookie_);
- handle_desc->trace_ended_ = true;
- }
- }
-
- // The following state detection is for debugging only.
- // We figure out if something is destroyed, uncreated, or live.
-
- // Does not distinguish between kTimedOutDestroyed and kDestroyed.
- bool IsDestroyed(Handle handle) const {
- auto it = states_.find(handle);
- if (it != states_.end()) {
- // Tracked values are not destroyed yet.
- return false;
- }
-
- if (handle == kInvalidHandle) {
- return false;
- }
-
- // The following assumes handles are incrementally generated:
- if (it == states_.end()) {
- // value is in range of [0, last_destroyed] => destroyed.
- return handle <= last_destroyed_;
- }
-
- auto min_it = states_.begin();
- if (handle < min_it->first) {
- // value smaller than anything tracked: it was destroyed and we stopped tracking it.
- return true;
- }
-
- auto max_it = states_.rbegin();
- if (handle > max_it->first) {
- // value too big: it's uncreated;
- return false;
- }
-
- // else it was a value that was previously tracked within [min,max] but no longer
- return true;
- }
-
- bool IsUncreated(Handle handle) const {
- auto it = states_.find(handle);
- if (it != states_.end()) {
- // Tracked values are not uncreated.
- return false;
- }
-
- if (handle == kInvalidHandle) {
- // Strangely enough, an invalid handle can never be created.
- return true;
- }
-
- // The following assumes handles are incrementally generated:
- if (it == states_.end()) {
- // value is in range of (last_destroyed, inf) => uncreated.
- return handle > last_destroyed_;
- }
-
- auto min_it = states_.begin();
- if (handle < min_it->first) {
- // value smaller than anything tracked: it was destroyed and we stopped tracking it.
- return false;
- }
-
- auto max_it = states_.rbegin();
- if (handle > max_it->first) {
- // value too big: it's uncreated;
- return true;
- }
-
- // else it was a value that was previously tracked within [min,max] but no longer
- return false;
- }
-
- public:
- void Dump(::android::Printer& printer) {
- // Locking can fail if we dump during a deadlock, so just do a best-effort lock here.
- bool is_it_locked = mutex_.try_lock();
-
- printer.printFormatLine("Perfetto consumer state:");
- if (!is_it_locked) {
- printer.printLine(""""" (possible deadlock)");
- }
- printer.printFormatLine(" Last destroyed handle: %" PRId64, last_destroyed_);
- printer.printFormatLine(" Last created handle: %" PRId64, last_created_);
- printer.printFormatLine("");
- printer.printFormatLine(" In-flight handles:");
-
- for (auto it = states_.begin(); it != states_.end(); ++it) {
- HandleDescription& handle_desc = it->second;
- uint64_t started_tracing =
- handle_desc.started_tracing_ns_ ? *handle_desc.started_tracing_ns_ : 0;
- printer.printFormatLine(" Handle %" PRId64, handle_desc.handle_);
- printer.printFormatLine(" Kind: %s", ToString(handle_desc.kind_).c_str());
- printer.printFormatLine(" Perfetto State: %d", static_cast<int>(handle_desc.state_));
- printer.printFormatLine(" Started tracing at: %" PRIu64, started_tracing);
- printer.printFormatLine(" Last transition at: %" PRIu64,
- handle_desc.last_transition_ns_);
- }
- if (states_.empty()) {
- printer.printFormatLine(" (None)");
- }
-
- printer.printFormatLine("");
-
- if (is_it_locked) { // u.b. if calling unlock on an unlocked mutex.
- mutex_.unlock();
- }
- }
-
- static PerfettoConsumerImpl::Impl* GetImplSingleton() {
- static PerfettoConsumerImpl::Impl impl;
- return &impl;
- }
-};
-
-// Use a singleton because fruit instantiates a new PerfettoConsumer object for every
-// new rx chain in RxProducerFactory. However, we want to track all perfetto transitions globally
-// through 1 impl object.
-//
-// TODO: Avoiding a singleton would mean a more significant refactoring to remove the fruit/perfetto
-// usage.
-
-
-//
-// Forward all calls to PerfettoConsumerImpl::Impl
-//
-
-PerfettoConsumerImpl::~PerfettoConsumerImpl() {
- // delete impl_; // TODO: no singleton
-}
-
-void PerfettoConsumerImpl::Initialize() {
- // impl_ = new PerfettoConsumerImpl::Impl(); // TODO: no singleton
- impl_ = PerfettoConsumerImpl::Impl::GetImplSingleton();
-}
-
-void PerfettoConsumerImpl::Dump(::android::Printer& printer) {
- PerfettoConsumerImpl::Impl::GetImplSingleton()->Dump(/*borrow*/printer);
-}
-
-PerfettoConsumer::Handle PerfettoConsumerImpl::Create(const void* config_proto,
- size_t config_len,
- PerfettoConsumer::OnStateChangedCb callback,
- void* callback_arg) {
- return impl_->Create(config_proto,
- config_len,
- callback,
- callback_arg);
-}
-
-void PerfettoConsumerImpl::StartTracing(PerfettoConsumer::Handle handle) {
- impl_->StartTracing(handle);
-}
-
-PerfettoConsumer::TraceBuffer PerfettoConsumerImpl::ReadTrace(PerfettoConsumer::Handle handle) {
- return impl_->ReadTrace(handle);
-}
-
-void PerfettoConsumerImpl::Destroy(PerfettoConsumer::Handle handle) {
- impl_->Destroy(handle);
-}
-
-PerfettoConsumer::State PerfettoConsumerImpl::PollState(PerfettoConsumer::Handle handle) {
- return impl_->PollState(handle);
-}
-
-} // namespace iorap::perfetto