// This specifies the maximum number of bits we need for encoding one entry. Each entry just // consists of a SLEB encoded value of method and action encodig which is a maximum of // sizeof(uintptr_t). static constexpr size_t kMaxBytesPerTraceEntry = sizeof(uintptr_t);
// We don't handle buffer overflows when processing the raw trace entries. We have a maximum of // kAlwaysOnTraceBufSize raw entries and we need a maximum of kMaxBytesPerTraceEntry to encode // each entry. To avoid overflow, we ensure that there are at least kMinBufSizeForEncodedData // bytes free space in the buffer. static constexpr size_t kMinBufSizeForEncodedData = kAlwaysOnTraceBufSize * kMaxBytesPerTraceEntry;
// TODO(mythria): 10 is a randomly chosen value. Tune it if required. static constexpr size_t kBufSizeForEncodedData = kMinBufSizeForEncodedData * 10;
void TraceProfiler::AllocateBuffer(Thread* thread) { if (!ShouldEnableProfileCode()) { return;
}
if (!profile_in_progress_) { return;
}
auto buffer = new uintptr_t[kAlwaysOnTraceBufSize];
size_t index = kAlwaysOnTraceBufSize; if (trace_data_->GetTraceType() == LowOverheadTraceType::kAllMethods) {
memset(buffer, 0, kAlwaysOnTraceBufSize * sizeof(uintptr_t));
} else {
DCHECK(trace_data_->GetTraceType() == LowOverheadTraceType::kLongRunningMethods); // For long running methods add a placeholder method exit entry. This avoids // additional checks on method exits to see if the previous entry is valid.
index--;
buffer[index] = 0x1;
}
thread->SetMethodTraceBuffer(buffer, index);
}
LowOverheadTraceType TraceProfiler::GetTraceType() {
MutexLock mu(Thread::Current(), *Locks::trace_lock_); if (Trace::IsTracingEnabledLocked()) {
DCHECK_EQ(trace_data_, nullptr); return Trace::GetTraceType();
} // LowOverhead trace entry points are configured based on the trace type. When trace_data_ is null // then there is no low overhead tracing running, so we use nop entry points. if (trace_data_ == nullptr) { return LowOverheadTraceType::kNone;
}
return trace_data_->GetTraceType();
}
namespace {
// Records the thread and method info. void DumpThreadMethodInfo(const std::unordered_map<size_t, std::string>& traced_threads, const std::unordered_set<ArtMethod*>& traced_methods,
std::ostringstream& os) REQUIRES_SHARED(Locks::mutator_lock_) { // Dump data about thread information. for (constauto& it : traced_threads) {
uint8_t thread_header[kAlwaysOnThreadInfoHeaderSize];
thread_header[0] = kThreadInfoHeaderV2;
Append4LE(thread_header + 1, it.first);
Append2LE(thread_header + 5, it.second.length());
os.write(reinterpret_cast<char*>(thread_header), kAlwaysOnThreadInfoHeaderSize);
os.write(it.second.c_str(), it.second.length());
}
std::string Base64Encode(std::string_view input) { // Encoding alphabet, see RFC4648 "Table 1: The Base 64 Alphabet" static constexpr char kTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; // 6 bits: 0b00111111 static constexpr int kBase64Mask = 0x3F;
std::string encoded_string; // The output size is roughly 4/3 the input size.
encoded_string.reserve(((input.size() + 2) / 3) * 4);
std::uint32_t accumulator = 0; // Tracks available bits. Starts at -6 because we need 6 bits for a usual-path extraction. int bits_in_temp = -6;
for (unsignedchar c : input) {
accumulator = (accumulator << 8) | c;
bits_in_temp += 8;
// While enough bits are available (>= 6) to extract a Base64 character while (bits_in_temp >= 0) { // Extract the most significant 6 available bits
encoded_string.push_back(kTable[(accumulator >> bits_in_temp) & kBase64Mask]);
bits_in_temp -= 6;
}
}
// Handle any remaining bits // note that the remaining bits equal 6 + bits_in_temp, so, for instance, // a bits_in_temp with the value -2 means 4 bits left, and that the only possible // values of bits_in_temp at this points are -6, -4 and -2 (ie, 0, 2 or 4 bits remain) if (bits_in_temp > -6) { // This shifts the remaining bits to the MSB side of the 6-bit chunk, // effectively padding with zeros on the right as per RFC 4648.
encoded_string.push_back(kTable[(accumulator << (-bits_in_temp)) & kBase64Mask]);
}
// Add padding characters to make the size a multiple of 4, per the RFC. while (encoded_string.size() % 4 != 0) {
encoded_string.push_back('=');
}
return encoded_string;
}
} // namespace
class TraceStopTask : public gc::HeapTask { public: explicit TraceStopTask(uint64_t target_run_time) : gc::HeapTask(target_run_time) {}
staticclass LongRunningMethodsTraceStartCheckpoint final : public Closure { public: void Run(Thread* thread) override REQUIRES_SHARED(Locks::mutator_lock_) { auto buffer = new uintptr_t[kAlwaysOnTraceBufSize];
thread->SetMethodTraceBuffer(buffer, kAlwaysOnTraceBufSize); // Record methods that are currently on stack.
TraceProfiler::ReportOnStackMethods(thread, [](ArtMethod* m, Thread* t, bool is_entry) {
TraceProfiler::RecordTraceEvent(m, t, is_entry);
}); // Record a placeholder method exit event into the buffer so we record method exits for the // methods that are currently on stack.
TraceProfiler::RecordTraceEvent(nullptr, thread, /*is_entry=*/false);
thread->UpdateTlsLowOverheadTraceEntrypoints(LowOverheadTraceType::kLongRunningMethods);
}
} long_running_methods_checkpoint_;
staticclass AllMethodsTraceStartCheckpoint final : public Closure { public: void Run(Thread* thread) override { auto buffer = new uintptr_t[kAlwaysOnTraceBufSize];
memset(buffer, 0, kAlwaysOnTraceBufSize * sizeof(uintptr_t));
thread->UpdateTlsLowOverheadTraceEntrypoints(LowOverheadTraceType::kAllMethods);
thread->SetMethodTraceBuffer(buffer, kAlwaysOnTraceBufSize);
}
} all_methods_checkpoint_;
void TraceProfiler::Start(LowOverheadTraceType trace_type, uint64_t trace_duration_ns) { if (!ShouldEnableProfileCode()) {
LOG(ERROR) << "Feature not supported. Please build with ALLOW_PROFILE_CODE and enable " "com.android.art.rw.flags.enable_profile_code_rw"; return;
}
{
MutexLock mu(self, *Locks::trace_lock_); if (Trace::IsTracingEnabledLocked()) {
LOG(ERROR) << "Cannot start a low-overehad trace when regular tracing is in progress"; return;
}
if (profile_in_progress_) { // We allow overlapping starts only when collecting long running methods. // If a trace of different type is in progress we ignore the request. if (trace_type == LowOverheadTraceType::kAllMethods ||
trace_data_->GetTraceType() != trace_type) {
LOG(ERROR) << "Profile already in progress. Ignoring this request"; return;
}
// For long running methods, just update the end time if there's a trace already in progress.
new_end_time = NanoTime() + trace_duration_ns; if (trace_data_->GetTraceEndTime() < new_end_time) {
trace_data_->SetTraceEndTime(new_end_time);
add_trace_end_task = true;
}
} else {
profile_in_progress_ = true;
trace_data_ = new TraceData(trace_type);
if (trace_type == LowOverheadTraceType::kAllMethods) { // TODO(mythria): Use Async trace events here. We don't have hooks for // these yet, so just use a ScopedTrace events for now.
ScopedTrace trace("LowOverheadTraceAll::Start");
runtime->GetThreadList()->RunCheckpoint(&all_methods_checkpoint_);
} else { // TODO(mythria): Use Async trace events here. We don't have hooks for // these yet, so just use a ScopedTrace events for now.
ScopedTrace("LowOverheadTraceLongRunning::Start");
runtime->GetThreadList()->RunCheckpoint(&long_running_methods_checkpoint_);
}
if (add_trace_end_task) { // Add a Task that stops the tracing after trace_duration.
runtime->GetHeap()->AddHeapTask(new TraceStopTask(new_end_time));
}
}
void TraceProfiler::StopLocked() { if (!profile_in_progress_) {
LOG(ERROR) << "No Profile in progress but a stop was requested"; return;
}
// TODO(mythria): Use Async trace events here. We don't have hooks for // these yet, so just use a ScopedTrace events for now.
ScopedTrace trace("LowOverheadTrace::Stop"); // We should not delete trace_data_ when there is an ongoing trace dump. So // wait for any in progress trace dump to finish.
trace_data_->MaybeWaitForTraceDumpToFinish();
// Create method entry events for all methods currently on the thread's stack. for (auto smi = visitor.stack_methods_.rbegin(); smi != visitor.stack_methods_.rend(); smi++) {
record_event(*smi, thread, /*is_entry=*/true);
}
}
size_t TraceProfiler::DumpBuffer(uint32_t thread_id,
uintptr_t* method_trace_entries,
uint8_t* buffer,
std::unordered_set<ArtMethod*>& methods) { // Encode header at the end once we compute the number of records.
uint8_t* curr_buffer_ptr = buffer + kAlwaysOnEntriesHeaderSize;
int num_records = 0;
uintptr_t prev_method_action_encoding = 0; int prev_action = -1; for (size_t i = kAlwaysOnTraceBufSize - 1; i > 0; i-=1) {
uintptr_t method_action_encoding = method_trace_entries[i]; // 0 value indicates the rest of the entries are empty. if (method_action_encoding == 0) { break;
}
int action = method_action_encoding & ~kMaskTraceAction;
int64_t diff; if (action == TraceAction::kTraceMethodEnter) {
diff = method_action_encoding - prev_method_action_encoding;
ArtMethod* method = reinterpret_cast<ArtMethod*>(method_action_encoding & kMaskTraceAction);
methods.insert(method);
} else { // On a method exit, we don't record the information about method. We just need a 1 in the // lsb and the method information can be derived from the last method that entered. To keep // the encoded value small just add the smallest value to make the lsb one. if (prev_action == TraceAction::kTraceMethodExit) {
diff = 0;
} else {
diff = 1;
}
}
curr_buffer_ptr = EncodeSignedLeb128(curr_buffer_ptr, diff);
num_records++;
prev_method_action_encoding = method_action_encoding;
prev_action = action;
}
// Fill in header information: // 1 byte of header identifier // 4 bytes of thread_id // 3 bytes of number of records
buffer[0] = kEntryHeaderV2;
Append4LE(buffer + 1, thread_id);
Append3LE(buffer + 5, num_records); return curr_buffer_ptr - buffer;
}
void TraceProfiler::Dump(int fd) { if (!ShouldEnableProfileCode()) {
LOG(ERROR) << "Feature not supported. Please build with ALLOW_PROFILE_CODE and enable " "com.android.art.rw.flags.enable_profile_code_rw"; return;
}
size_t threads_running_checkpoint = 0;
std::unique_ptr<TraceDumpCheckpoint> checkpoint;
{
MutexLock mu(self, *Locks::trace_lock_); if (!profile_in_progress_ || trace_data_->IsTraceDumpInProgress()) { if (trace_file != nullptr && !trace_file->Close()) {
PLOG(WARNING) << "Failed to close file.";
} return;
}
trace_data_->SetTraceDumpInProgress();
// Collect long running methods from all the threads;
checkpoint.reset(new TraceDumpCheckpoint(trace_data_, trace_file));
threads_running_checkpoint = runtime->GetThreadList()->RunCheckpoint(checkpoint.get());
}
// Add a header packet with end time stamp and a monotonic timer.
uint8_t trace_header[kAlwaysOnTraceHeaderSize];
trace_header[0] = kSummaryHeaderV2;
Append8LE(trace_header + 1, end_timestamp);
Append8LE(trace_header + 9, monotonic_timer);
os.write(reinterpret_cast<char*>(trace_header), kAlwaysOnTraceHeaderSize);
// Wait for all threads to dump their data. if (threads_running_checkpoint != 0) {
checkpoint->WaitForThreadsToRunThroughCheckpoint(threads_running_checkpoint);
}
checkpoint->FinishTraceDump(os);
if (trace_file != nullptr) {
std::string info = os.str(); if (!trace_file->WriteFully(info.c_str(), info.length())) {
PLOG(WARNING) << "Failed writing information to file";
}
if (!trace_file->Close()) {
PLOG(WARNING) << "Failed to close file.";
}
}
}
void TraceProfiler::ReleaseThreadBuffer(Thread* self) { if (!IsTraceProfileInProgress()) { return;
} // TODO(mythria): Maybe it's good to cache these and dump them when requested. For now just // relese the buffer when a thread is exiting. auto buffer = self->GetMethodTraceBuffer(); delete[] buffer;
self->SetMethodTraceBuffer(nullptr, 0);
}
void TraceProfiler::TraceTimeElapsed() {
MutexLock mu(Thread::Current(), *Locks::trace_lock_);
DCHECK_IMPLIES(!profile_in_progress_, trace_data_ != nullptr); if (!profile_in_progress_ || trace_data_->GetTraceEndTime() > NanoTime()) { // The end duration was extended by another start, so just ignore this task. return;
}
TraceProfiler::StopLocked();
}
size_t TraceProfiler::DumpLongRunningMethodBuffer(uint32_t thread_id,
uintptr_t* method_trace_entries,
uintptr_t* end_trace_entries,
uint8_t* buffer,
std::unordered_set<ArtMethod*>& methods) { // Encode header at the end once we compute the number of records.
uint8_t* curr_buffer_ptr = buffer + kAlwaysOnEntriesHeaderSize;
int num_records = 0;
uintptr_t prev_time_action_encoding = 0;
uintptr_t prev_method_ptr = 0;
int64_t end_index = end_trace_entries - method_trace_entries; for (int64_t i = kAlwaysOnTraceBufSize; i > end_index;) {
uintptr_t event = method_trace_entries[--i]; if (event == 0x1) { // This is a placeholder event. Ignore this event. continue;
}
// Fill in header information: // 1 byte of header identifier // 4 bytes of thread_id // 3 bytes of number of records // 4 bytes the size of the data
buffer[0] = kEntryHeaderV2;
Append4LE(buffer + 1, thread_id);
Append3LE(buffer + 5, num_records);
size_t size = curr_buffer_ptr - buffer;
Append4LE(buffer + 8, size - kAlwaysOnEntriesHeaderSize); return curr_buffer_ptr - buffer;
}
{ // Check if low-overhead tracing is in progress. We may have non-null buffer // if we are doing regular method tracing.
MutexLock mu(Thread::Current(), *Locks::trace_lock_); if (!IsTraceProfileInProgress()) { return;
}
}
RecordTraceEvent(method, thread, is_entry);
}
uintptr_t** method_trace_curr_ptr = thread->GetTraceBufferCurrEntryPtr();
size_t index = *method_trace_curr_ptr - method_trace_entries;
size_t num_bytes = 0;
std::unique_ptr<uint8_t[]> buffer_ptr; // Check if there is sufficient space to record entries. We start recording from the end of the // buffer so the current index indicates the number of remaining entries. if (index < kMaxEntriesForRecordingEvent) { // Find the last method exit event. We can flush all the entries before this event. We cannot // flush remaining events because we haven't determined if they are long running or not.
uintptr_t* processed_events_ptr = nullptr; for (uintptr_t* ptr = *method_trace_curr_ptr;
ptr < method_trace_entries + kAlwaysOnTraceBufSize;) { if (*ptr & 0x1) { // Method exit. We need to keep events until (including this method exit) here.
processed_events_ptr = ptr + 1; break;
}
ptr += 2;
}
size_t num_occupied_entries = (processed_events_ptr - *method_trace_curr_ptr);
index = kAlwaysOnTraceBufSize;
buffer_ptr.reset(new uint8_t[kBufSizeForEncodedData]); if (num_occupied_entries > kMaxEntriesAfterFlush) { // If we don't have sufficient space just record a placeholder exit and flush all the existing // events. We have accurate timestamps to filter out these events in a post-processing step. // This would happen only when we have very deeply (~1024) nested code.
num_bytes = DumpLongRunningMethodBuffer(thread->GetTid(),
method_trace_entries,
*method_trace_curr_ptr,
buffer_ptr.get(),
traced_methods);
// Encode a placeholder exit event. This will be ignored when dumping the methods.
method_trace_entries[--index] = 0x1;
} else { // Flush all the entries till the method exit event.
num_bytes = DumpLongRunningMethodBuffer(thread->GetTid(),
method_trace_entries,
processed_events_ptr,
buffer_ptr.get(),
traced_methods);
// Move the remaining events to the start of the buffer. for (uintptr_t* ptr = processed_events_ptr - 1; ptr >= *method_trace_curr_ptr; ptr--) {
method_trace_entries[--index] = *ptr;
}
}
}
if (num_bytes > 0) {
MutexLock mu(Thread::Current(), *Locks::trace_lock_); // When clearing trace_data_, we install a checkpoint to clear per-thread buffer pointer but do // not wait for all threads to run the checkpoint. This allows short pause when stopping the // trace. This means that, there could be cases where the per-thread buffer is still non-null // but we have deleted the trace_data_. So it is required to check if the profile is still in // progress. if (!profile_in_progress_) { // Clear the per-thread buffer. The checkpoint can handle cases where the buffer pointer is // already cleared so it is safe to clear it here. delete[] method_trace_entries;
thread->SetMethodTraceBuffer(/* buffer= */ nullptr, /* offset= */ 0); return;
}
trace_data_->AppendToLongRunningMethods(buffer_ptr.get(), num_bytes);
trace_data_->AddTracedMethods(traced_methods);
trace_data_->AddTracedThread(thread);
}
}
std::string TraceProfiler::GetLongRunningMethodsString() { if (!ShouldEnableProfileCode()) { return std::string();
}
void TraceDumpCheckpoint::FinishTraceDump(std::ostringstream& os) { // Dump all the data.
trace_data_->DumpData(os);
// Any trace stop requests will be blocked while a dump is in progress. So // broadcast the completion condition for any waiting requests.
MutexLock mu(Thread::Current(), *Locks::trace_lock_);
trace_data_->SignalTraceDumpComplete();
}
void TraceData::DumpData(std::ostringstream& os) {
std::unordered_set<ArtMethod*> methods;
std::unordered_map<size_t, std::string> threads;
{ // We cannot dump method information while holding trace_lock_, since we have to also // acquire a mutator lock. Take a snapshot of thread and method information.
MutexLock mu(Thread::Current(), trace_data_lock_); if (curr_buffer_ != nullptr) { for (size_t i = 0; i < overflow_buffers_.size(); i++) {
os.write(reinterpret_cast<char*>(overflow_buffers_[i].get()), kBufSizeForEncodedData);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.