blob: 4fe677b231fcfda53d3ee5931332307d4fe462d1 [file] [log] [blame]
// 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