diff options
| -rw-r--r-- | runtime/Android.bp | 2 | ||||
| -rw-r--r-- | runtime/native/dalvik_system_VMDebug.cc | 45 | ||||
| -rw-r--r-- | runtime/thread.h | 4 | ||||
| -rw-r--r-- | runtime/trace.cc | 148 | ||||
| -rw-r--r-- | runtime/trace.h | 57 | ||||
| -rw-r--r-- | runtime/trace_profile.cc | 207 | ||||
| -rw-r--r-- | runtime/trace_profile.h | 74 | ||||
| -rw-r--r-- | tools/cpp-define-generator/thread.def | 2 |
8 files changed, 440 insertions, 99 deletions
diff --git a/runtime/Android.bp b/runtime/Android.bp index 244daeae2c..0d9009924a 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -415,6 +415,7 @@ cc_defaults { "thread_pool.cc", "ti/agent.cc", "trace.cc", + "trace_profile.cc", "var_handles.cc", "vdex_file.cc", "verifier/class_verifier.cc", @@ -782,6 +783,7 @@ gensrcs { "thread.h", "thread_state.h", "trace.h", + "trace_profile.h", "verifier/verifier_enums.h", ], output_extension: "operator_out.cc", diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc index 18120fe826..11d2898c3c 100644 --- a/runtime/native/dalvik_system_VMDebug.cc +++ b/runtime/native/dalvik_system_VMDebug.cc @@ -50,6 +50,7 @@ #include "string_array_utils.h" #include "thread-inl.h" #include "trace.h" +#include "trace_profile.h" namespace art HIDDEN { @@ -153,6 +154,46 @@ static void VMDebug_stopMethodTracing(JNIEnv*, jclass) { Trace::Stop(); } +static void VMDebug_stopLowOverheadTraceImpl(JNIEnv*, jclass) { + TraceProfiler::Stop(); +} + +static void VMDebug_dumpLowOverheadTraceImpl(JNIEnv* env, jclass, jstring javaProfileFileName) { + ScopedUtfChars profileFileName(env, javaProfileFileName); + if (profileFileName.c_str() == nullptr) { + LOG(ERROR) << "Filename not provided, ignoring the request to dump profile"; + return; + } + TraceProfiler::Dump(profileFileName.c_str()); +} + +static void VMDebug_dumpLowOverheadTraceFdImpl(JNIEnv* env, jclass, jint originalFd) { + if (originalFd < 0) { + ScopedObjectAccess soa(env); + soa.Self()->ThrowNewExceptionF("Ljava/lang/RuntimeException;", + "Trace fd is invalid: %d", + originalFd); + return; + } + + // Set the O_CLOEXEC flag atomically here, so the file gets closed when a new process is forked. + int fd = DupCloexec(originalFd); + if (fd < 0) { + ScopedObjectAccess soa(env); + soa.Self()->ThrowNewExceptionF("Ljava/lang/RuntimeException;", + "dup(%d) failed: %s", + originalFd, + strerror(errno)); + return; + } + + TraceProfiler::Dump(fd); +} + +static void VMDebug_startLowOverheadTraceImpl(JNIEnv*, jclass) { + TraceProfiler::Start(); +} + static jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) { // This function will be replaced by the debugger when it's connected. See // external/oj-libjdwp/src/share/vmDebug.c for implementation when debugger is connected. @@ -593,6 +634,10 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(VMDebug, addApplication, "(Ljava/lang/String;)V"), NATIVE_METHOD(VMDebug, removeApplication, "(Ljava/lang/String;)V"), NATIVE_METHOD(VMDebug, setUserId, "(I)V"), + NATIVE_METHOD(VMDebug, startLowOverheadTraceImpl, "()V"), + NATIVE_METHOD(VMDebug, stopLowOverheadTraceImpl, "()V"), + NATIVE_METHOD(VMDebug, dumpLowOverheadTraceImpl, "(Ljava/lang/String;)V"), + NATIVE_METHOD(VMDebug, dumpLowOverheadTraceFdImpl, "(I)V"), }; void register_dalvik_system_VMDebug(JNIEnv* env) { diff --git a/runtime/thread.h b/runtime/thread.h index 5496272026..da9a70d8b1 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -1366,10 +1366,10 @@ class EXPORT Thread { void SetMethodTraceBuffer(uintptr_t* buffer, int init_index) { tlsPtr_.method_trace_buffer = buffer; - SetTraceBufferCurrentEntry(init_index); + SetMethodTraceBufferCurrentEntry(init_index); } - void SetTraceBufferCurrentEntry(int index) { + void SetMethodTraceBufferCurrentEntry(int index) { uintptr_t* buffer = tlsPtr_.method_trace_buffer; if (buffer == nullptr) { tlsPtr_.method_trace_buffer_curr_entry = nullptr; diff --git a/runtime/trace.cc b/runtime/trace.cc index 15bea9df12..37b14e468e 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -50,6 +50,7 @@ #include "stack.h" #include "thread.h" #include "thread_list.h" +#include "trace_profile.h" namespace art HIDDEN { @@ -77,20 +78,6 @@ static const uint16_t kTraceRecordSizeSingleClock = 10; // using v2 static const uint16_t kTraceRecordSizeDualClock = 14; // using v3 with two timestamps static const size_t kNumTracePoolBuffers = 32; -// Packet type encoding for the new method tracing format. -static const int kThreadInfoHeaderV2 = 0; -static const int kMethodInfoHeaderV2 = 1; -static const int kEntryHeaderV2 = 2; -static const int kSummaryHeaderV2 = 3; - -// Packet sizes for the new method trace format. -static const uint16_t kTraceHeaderLengthV2 = 32; -static const uint16_t kTraceRecordSizeSingleClockV2 = 6; -static const uint16_t kTraceRecordSizeDualClockV2 = kTraceRecordSizeSingleClockV2 + 2; -static const uint16_t kEntryHeaderSizeV2 = 12; - -static const uint16_t kTraceVersionSingleClockV2 = 4; -static const uint16_t kTraceVersionDualClockV2 = 5; static constexpr size_t kMinBufSize = 18U; // Trace header is up to 18B. // Size of per-thread buffer size. The value is chosen arbitrarily. This value @@ -458,39 +445,6 @@ uint32_t Trace::GetClockOverheadNanoSeconds() { return static_cast<uint32_t>(elapsed_us / 32); } -// TODO: put this somewhere with the big-endian equivalent used by JDWP. -static void Append2LE(uint8_t* buf, uint16_t val) { - *buf++ = static_cast<uint8_t>(val); - *buf++ = static_cast<uint8_t>(val >> 8); -} - -// TODO: put this somewhere with the big-endian equivalent used by JDWP. -static void Append3LE(uint8_t* buf, uint16_t val) { - *buf++ = static_cast<uint8_t>(val); - *buf++ = static_cast<uint8_t>(val >> 8); - *buf++ = static_cast<uint8_t>(val >> 16); -} - -// TODO: put this somewhere with the big-endian equivalent used by JDWP. -static void Append4LE(uint8_t* buf, uint32_t val) { - *buf++ = static_cast<uint8_t>(val); - *buf++ = static_cast<uint8_t>(val >> 8); - *buf++ = static_cast<uint8_t>(val >> 16); - *buf++ = static_cast<uint8_t>(val >> 24); -} - -// TODO: put this somewhere with the big-endian equivalent used by JDWP. -static void Append8LE(uint8_t* buf, uint64_t val) { - *buf++ = static_cast<uint8_t>(val); - *buf++ = static_cast<uint8_t>(val >> 8); - *buf++ = static_cast<uint8_t>(val >> 16); - *buf++ = static_cast<uint8_t>(val >> 24); - *buf++ = static_cast<uint8_t>(val >> 32); - *buf++ = static_cast<uint8_t>(val >> 40); - *buf++ = static_cast<uint8_t>(val >> 48); - *buf++ = static_cast<uint8_t>(val >> 56); -} - static void GetSample(Thread* thread, void* arg) REQUIRES_SHARED(Locks::mutator_lock_) { std::vector<ArtMethod*>* const stack_trace = Trace::AllocStackTrace(); StackVisitor::WalkStack( @@ -839,52 +793,58 @@ void Trace::Start(std::unique_ptr<File>&& trace_file_in, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa(__FUNCTION__); MutexLock mu(self, *Locks::trace_lock_); - if (the_trace_ != nullptr) { + if (TraceProfiler::IsTraceProfileInProgress()) { + LOG(ERROR) << "On-demand profile in progress, ignoring this request"; + return; + } + + if (Trace::IsTracingEnabledLocked()) { LOG(ERROR) << "Trace already in progress, ignoring this request"; + return; + } + + enable_stats = (flags & kTraceCountAllocs) != 0; + bool is_trace_format_v2 = GetTraceFormatVersionFromFlags(flags) == Trace::kFormatV2; + the_trace_ = new Trace(trace_file.release(), buffer_size, flags, output_mode, trace_mode); + num_trace_starts_++; + if (is_trace_format_v2) { + // Record all the methods that are currently loaded. We log all methods when any new class + // is loaded. This will allow us to process the trace entries without requiring a mutator + // lock. + RecordMethodInfoClassVisitor visitor(the_trace_); + runtime->GetClassLinker()->VisitClasses(&visitor); + visitor.FlushBuffer(); + } + if (trace_mode == TraceMode::kSampling) { + CHECK_PTHREAD_CALL(pthread_create, (&sampling_pthread_, nullptr, &RunSamplingThread, + reinterpret_cast<void*>(interval_us)), + "Sampling profiler thread"); + the_trace_->interval_us_ = interval_us; } else { - enable_stats = (flags & kTraceCountAllocs) != 0; - int trace_format_version = GetTraceFormatVersionFromFlags(flags); - the_trace_ = new Trace(trace_file.release(), buffer_size, flags, output_mode, trace_mode); - num_trace_starts_++; - if (trace_format_version == Trace::kFormatV2) { - // Record all the methods that are currently loaded. We log all methods when any new class - // is loaded. This will allow us to process the trace entries without requiring a mutator - // lock. - RecordMethodInfoClassVisitor visitor(the_trace_); - runtime->GetClassLinker()->VisitClasses(&visitor); - visitor.FlushBuffer(); - } - if (trace_mode == TraceMode::kSampling) { - CHECK_PTHREAD_CALL(pthread_create, (&sampling_pthread_, nullptr, &RunSamplingThread, - reinterpret_cast<void*>(interval_us)), - "Sampling profiler thread"); - the_trace_->interval_us_ = interval_us; - } else { - if (!runtime->IsJavaDebuggable()) { - art::jit::Jit* jit = runtime->GetJit(); - if (jit != nullptr) { - jit->GetCodeCache()->InvalidateAllCompiledCode(); - jit->GetCodeCache()->TransitionToDebuggable(); - jit->GetJitCompiler()->SetDebuggableCompilerOption(true); - } - runtime->SetRuntimeDebugState(art::Runtime::RuntimeDebugState::kJavaDebuggable); - runtime->GetInstrumentation()->UpdateEntrypointsForDebuggable(); - runtime->DeoptimizeBootImage(); - } - if (trace_format_version == Trace::kFormatV2) { - // Add ClassLoadCallback to record methods on class load. - runtime->GetRuntimeCallbacks()->AddClassLoadCallback(the_trace_); + if (!runtime->IsJavaDebuggable()) { + art::jit::Jit* jit = runtime->GetJit(); + if (jit != nullptr) { + jit->GetCodeCache()->InvalidateAllCompiledCode(); + jit->GetCodeCache()->TransitionToDebuggable(); + jit->GetJitCompiler()->SetDebuggableCompilerOption(true); } - runtime->GetInstrumentation()->AddListener( - the_trace_, - instrumentation::Instrumentation::kMethodEntered | - instrumentation::Instrumentation::kMethodExited | - instrumentation::Instrumentation::kMethodUnwind, - UseFastTraceListeners(the_trace_->GetClockSource())); - runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey, - the_trace_, - /*needs_interpreter=*/false); + runtime->SetRuntimeDebugState(art::Runtime::RuntimeDebugState::kJavaDebuggable); + runtime->GetInstrumentation()->UpdateEntrypointsForDebuggable(); + runtime->DeoptimizeBootImage(); + } + if (is_trace_format_v2) { + // Add ClassLoadCallback to record methods on class load. + runtime->GetRuntimeCallbacks()->AddClassLoadCallback(the_trace_); } + runtime->GetInstrumentation()->AddListener( + the_trace_, + instrumentation::Instrumentation::kMethodEntered | + instrumentation::Instrumentation::kMethodExited | + instrumentation::Instrumentation::kMethodUnwind, + UseFastTraceListeners(the_trace_->GetClockSource())); + runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey, + the_trace_, + /*needs_interpreter=*/false); } } @@ -1679,7 +1639,7 @@ void TraceWriter::FlushBuffer(Thread* thread, bool is_sync, bool release) { if (release) { thread->SetMethodTraceBuffer(nullptr, 0); } else { - thread->SetTraceBufferCurrentEntry(kPerThreadBufSize); + thread->SetMethodTraceBufferCurrentEntry(kPerThreadBufSize); } } else { int old_index = GetMethodTraceIndex(method_trace_entries); @@ -1892,7 +1852,7 @@ void Trace::LogMethodTraceEvent(Thread* thread, if (trace_writer_->HasOverflow()) { // In non-streaming modes, we stop recoding events once the buffer is full. Just reset the // index, so we don't go to runtime for each method. - thread->SetTraceBufferCurrentEntry(kPerThreadBufSize); + thread->SetMethodTraceBufferCurrentEntry(kPerThreadBufSize); return; } @@ -1902,7 +1862,7 @@ void Trace::LogMethodTraceEvent(Thread* thread, // more entries. In streaming mode, it returns nullptr if it fails to allocate a new buffer. method_trace_buffer = trace_writer_->PrepareBufferForNewEntries(thread); if (method_trace_buffer == nullptr) { - thread->SetTraceBufferCurrentEntry(kPerThreadBufSize); + thread->SetMethodTraceBufferCurrentEntry(kPerThreadBufSize); return; } } @@ -2023,4 +1983,8 @@ bool Trace::IsTracingEnabled() { return the_trace_ != nullptr; } +bool Trace::IsTracingEnabledLocked() { + return the_trace_ != nullptr; +} + } // namespace art diff --git a/runtime/trace.h b/runtime/trace.h index b02bdc36e4..445cd7e3af 100644 --- a/runtime/trace.h +++ b/runtime/trace.h @@ -94,10 +94,6 @@ std::ostream& operator<<(std::ostream& os, TracingMode rhs); // // All values are stored in little-endian order. -// TODO(mythria): A randomly chosen value. Tune it later based on the number of -// entries required in the buffer. -static constexpr size_t kAlwaysOnTraceBufSize = 2048; - enum TraceAction { kTraceMethodEnter = 0x00, // method entry kTraceMethodExit = 0x01, // method exit @@ -128,6 +124,56 @@ static constexpr int32_t kHighTimestampOffsetInBytes = static constexpr uintptr_t kMaskTraceAction = ~0b11; +// Packet type encoding for the new method tracing format. +static constexpr int kThreadInfoHeaderV2 = 0; +static constexpr int kMethodInfoHeaderV2 = 1; +static constexpr int kEntryHeaderV2 = 2; +static constexpr int kSummaryHeaderV2 = 3; + +// Packet sizes for the new method tracing format. +static constexpr uint16_t kTraceHeaderLengthV2 = 32; +static constexpr uint16_t kTraceRecordSizeSingleClockV2 = 6; +static constexpr uint16_t kTraceRecordSizeDualClockV2 = kTraceRecordSizeSingleClockV2 + 2; +static constexpr uint16_t kEntryHeaderSizeV2 = 12; + +static constexpr uint16_t kTraceVersionSingleClockV2 = 4; +static constexpr uint16_t kTraceVersionDualClockV2 = 5; + +// TODO(mythria): Consider adding checks to guard agaist OOB access for Append*LE methods. +// Currently the onus is on the callers to ensure there is sufficient space in the buffer. +// TODO: put this somewhere with the big-endian equivalent used by JDWP. +static inline void Append2LE(uint8_t* buf, uint16_t val) { + *buf++ = static_cast<uint8_t>(val); + *buf++ = static_cast<uint8_t>(val >> 8); +} + +// TODO: put this somewhere with the big-endian equivalent used by JDWP. +static inline void Append3LE(uint8_t* buf, uint16_t val) { + *buf++ = static_cast<uint8_t>(val); + *buf++ = static_cast<uint8_t>(val >> 8); + *buf++ = static_cast<uint8_t>(val >> 16); +} + +// TODO: put this somewhere with the big-endian equivalent used by JDWP. +static inline void Append4LE(uint8_t* buf, uint32_t val) { + *buf++ = static_cast<uint8_t>(val); + *buf++ = static_cast<uint8_t>(val >> 8); + *buf++ = static_cast<uint8_t>(val >> 16); + *buf++ = static_cast<uint8_t>(val >> 24); +} + +// TODO: put this somewhere with the big-endian equivalent used by JDWP. +static inline void Append8LE(uint8_t* buf, uint64_t val) { + *buf++ = static_cast<uint8_t>(val); + *buf++ = static_cast<uint8_t>(val >> 8); + *buf++ = static_cast<uint8_t>(val >> 16); + *buf++ = static_cast<uint8_t>(val >> 24); + *buf++ = static_cast<uint8_t>(val >> 32); + *buf++ = static_cast<uint8_t>(val >> 40); + *buf++ = static_cast<uint8_t>(val >> 48); + *buf++ = static_cast<uint8_t>(val >> 56); +} + class TraceWriterThreadPool : public ThreadPool { public: static TraceWriterThreadPool* Create(const char* name) { @@ -534,6 +580,9 @@ class Trace final : public instrumentation::InstrumentationListener, public Clas // Used by class linker to prevent class unloading. static bool IsTracingEnabled() REQUIRES(!Locks::trace_lock_); + // Used by the profiler to see if there is any ongoing tracing. + static bool IsTracingEnabledLocked() REQUIRES(Locks::trace_lock_); + // Callback for each class prepare event to record information about the newly created methods. static void ClassPrepare(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/trace_profile.cc b/runtime/trace_profile.cc new file mode 100644 index 0000000000..c1000d2095 --- /dev/null +++ b/runtime/trace_profile.cc @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2024 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 "trace_profile.h" + +#include "base/leb128.h" +#include "base/mutex.h" +#include "base/unix_file/fd_file.h" +#include "com_android_art_flags.h" +#include "runtime.h" +#include "thread-current-inl.h" +#include "thread.h" +#include "thread_list.h" +#include "trace.h" + +namespace art_flags = com::android::art::flags; + +namespace art HIDDEN { + +// 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; + +static constexpr size_t kAlwaysOnTraceHeaderSize = 8; + +bool TraceProfiler::profile_in_progress_ = false; + +void TraceProfiler::Start() { + if (!art_flags::always_enable_profile_code()) { + LOG(ERROR) << "Feature not supported. Please build with ART_ALWAYS_ENABLE_PROFILE_CODE."; + return; + } + + Thread* self = Thread::Current(); + MutexLock mu(self, *Locks::trace_lock_); + if (profile_in_progress_) { + LOG(ERROR) << "Profile already in progress. Ignoring this request"; + return; + } + + if (Trace::IsTracingEnabledLocked()) { + LOG(ERROR) << "Cannot start a profile when method tracing is in progress"; + return; + } + + profile_in_progress_ = true; + + ScopedSuspendAll ssa(__FUNCTION__); + MutexLock tl(self, *Locks::thread_list_lock_); + for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) { + auto buffer = new uintptr_t[kAlwaysOnTraceBufSize]; + memset(buffer, 0, kAlwaysOnTraceBufSize * sizeof(uintptr_t)); + thread->SetMethodTraceBuffer(buffer, kAlwaysOnTraceBufSize); + } +} + +void TraceProfiler::Stop() { + if (!art_flags::always_enable_profile_code()) { + LOG(ERROR) << "Feature not supported. Please build with ART_ALWAYS_ENABLE_PROFILE_CODE."; + return; + } + + Thread* self = Thread::Current(); + MutexLock mu(self, *Locks::trace_lock_); + if (!profile_in_progress_) { + LOG(ERROR) << "No Profile in progress but a stop was requested"; + return; + } + + ScopedSuspendAll ssa(__FUNCTION__); + MutexLock tl(self, *Locks::thread_list_lock_); + for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) { + auto buffer = thread->GetMethodTraceBuffer(); + if (buffer != nullptr) { + delete[] buffer; + thread->SetMethodTraceBuffer(/* buffer= */ nullptr, /* offset= */ 0); + } + } + + profile_in_progress_ = false; +} + +uint8_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 + kAlwaysOnTraceHeaderSize; + + int num_records = 0; + uintptr_t prev_method_action_encoding = 0; + for (size_t i = 0; i < kAlwaysOnTraceBufSize; i++) { + uintptr_t method_action_encoding = method_trace_entries[num_records]; + // 0 value indicates the rest of the entries are empty. + if (method_action_encoding == 0) { + break; + } + + int64_t method_diff = method_action_encoding - prev_method_action_encoding; + curr_buffer_ptr = EncodeSignedLeb128(curr_buffer_ptr, method_diff); + + ArtMethod* method = reinterpret_cast<ArtMethod*>(method_action_encoding & kMaskTraceAction); + methods.insert(method); + num_records++; + prev_method_action_encoding = method_action_encoding; + } + + // 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; +} + +void TraceProfiler::Dump(int fd) { + if (!art_flags::always_enable_profile_code()) { + LOG(ERROR) << "Feature not supported. Please build with ART_ALWAYS_ENABLE_PROFILE_CODE."; + return; + } + + std::unique_ptr<File> trace_file(new File(fd, /*check_usage=*/true)); + Dump(std::move(trace_file)); +} + +void TraceProfiler::Dump(const char* filename) { + if (!art_flags::always_enable_profile_code()) { + LOG(ERROR) << "Feature not supported. Please build with ART_ALWAYS_ENABLE_PROFILE_CODE."; + return; + } + + std::unique_ptr<File> trace_file(OS::CreateEmptyFileWriteOnly(filename)); + if (trace_file == nullptr) { + PLOG(ERROR) << "Unable to open trace file " << filename; + return; + } + + Dump(std::move(trace_file)); +} + +void TraceProfiler::Dump(std::unique_ptr<File>&& trace_file) { + Thread* self = Thread::Current(); + std::unordered_set<ArtMethod*> traced_methods; + MutexLock mu(self, *Locks::trace_lock_); + if (!profile_in_progress_) { + LOG(ERROR) << "No Profile in progress. Nothing to dump."; + return; + } + + ScopedSuspendAll ssa(__FUNCTION__); + MutexLock tl(self, *Locks::thread_list_lock_); + uint8_t* buffer_ptr = new uint8_t[kBufSizeForEncodedData]; + uint8_t* curr_buffer_ptr = buffer_ptr; + for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) { + auto method_trace_entries = thread->GetMethodTraceBuffer(); + if (method_trace_entries == nullptr) { + continue; + } + + size_t offset = curr_buffer_ptr - buffer_ptr; + if (offset >= kMinBufSizeForEncodedData) { + if (!trace_file->WriteFully(buffer_ptr, offset)) { + PLOG(WARNING) << "Failed streaming a tracing event."; + } + curr_buffer_ptr = buffer_ptr; + } + curr_buffer_ptr = + DumpBuffer(thread->GetTid(), method_trace_entries, curr_buffer_ptr, traced_methods); + // Reset the buffer and continue profiling. We need to set the buffer to + // zeroes, since we use a circular buffer and detect empty entries by + // checking for zeroes. + memset(method_trace_entries, 0, kAlwaysOnTraceBufSize * sizeof(uintptr_t)); + // Reset the current pointer. + thread->SetMethodTraceBufferCurrentEntry(kAlwaysOnTraceBufSize); + } +} + +bool TraceProfiler::IsTraceProfileInProgress() { + return profile_in_progress_; +} + +} // namespace art diff --git a/runtime/trace_profile.h b/runtime/trace_profile.h new file mode 100644 index 0000000000..7118cd5882 --- /dev/null +++ b/runtime/trace_profile.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 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. + */ + +#ifndef ART_RUNTIME_TRACE_PROFILE_H_ +#define ART_RUNTIME_TRACE_PROFILE_H_ + +#include <unordered_set> + +#include "base/locks.h" +#include "base/macros.h" +#include "base/os.h" + +namespace art HIDDEN { + +class ArtMethod; + +// TODO(mythria): A randomly chosen value. Tune it later based on the number of +// entries required in the buffer. +static constexpr size_t kAlwaysOnTraceBufSize = 2048; + +// This class implements low-overhead tracing. This feature is available only when +// always_enable_profile_code is enabled which is a build time flag defined in +// build/flags/art-flags.aconfig. When this flag is enabled, AOT and JITed code can record events +// on each method execution. When a profile is started, method entry / exit events are recorded in +// a per-thread circular buffer. When requested the recorded events in the buffer are dumped into a +// file. The buffers are released when the profile is stopped. +class TraceProfiler { + public: + // Starts profiling by allocating a per-thread buffer for all the threads. + static void Start(); + + // Releases all the buffers. + static void Stop(); + + // Dumps the recorded events in the buffer from all threads in the specified file. + static void Dump(int fd); + static void Dump(const char* trace_filename); + + static bool IsTraceProfileInProgress() REQUIRES(Locks::trace_lock_); + + private: + // Dumps the events from all threads into the trace_file. + static void Dump(std::unique_ptr<File>&& trace_file); + + // This method goes over all the events in the thread_buffer and stores the encoded event in the + // buffer. It returns the pointer to the next free entry in the buffer. + // This also records the ArtMethods from the events in the thread_buffer in a set. This set is + // used to dump the information about the methods once buffers from all threads have been + // processed. + static uint8_t* DumpBuffer(uint32_t thread_id, + uintptr_t* thread_buffer, + uint8_t* buffer /* out */, + std::unordered_set<ArtMethod*>& methods /* out */); + + static bool profile_in_progress_ GUARDED_BY(Locks::trace_lock_); + DISALLOW_COPY_AND_ASSIGN(TraceProfiler); +}; + +} // namespace art + +#endif // ART_RUNTIME_TRACE_PROFILE_H_ diff --git a/tools/cpp-define-generator/thread.def b/tools/cpp-define-generator/thread.def index 0d1860d549..aa621697c8 100644 --- a/tools/cpp-define-generator/thread.def +++ b/tools/cpp-define-generator/thread.def @@ -17,7 +17,7 @@ #if ASM_DEFINE_INCLUDE_DEPENDENCIES #include "entrypoints/quick/quick_entrypoints_enum.h" #include "thread.h" -#include "trace.h" +#include "trace_profile.h" #endif ASM_DEFINE(THREAD_CARD_TABLE_OFFSET, |