summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mythri Alle <mythria@google.com> 2024-07-12 10:52:14 +0000
committer Mythri Alle <mythria@google.com> 2024-09-11 08:23:21 +0000
commit88c7e963f7861a1b92742150010ca7253f10023f (patch)
tree199188b251290acb229dc07acc08db30be27a175
parentb602b6e057d23d3550e9900b6bd7207ed1fcbf44 (diff)
Adds an interface to collect on demand art traces
This CL adds implementation of interface to collect dex method traces when requested. This is different from method tracing. This doesn't provide precise traces and uses a circular buffer so it only contains a fixed number of latest entries based on the size of buffer. Bug: 352518093 Test: art/test.py Change-Id: If3e7935e1e99eabeef0dc448d2ba3541d0da650d
-rw-r--r--runtime/Android.bp2
-rw-r--r--runtime/native/dalvik_system_VMDebug.cc45
-rw-r--r--runtime/thread.h4
-rw-r--r--runtime/trace.cc148
-rw-r--r--runtime/trace.h57
-rw-r--r--runtime/trace_profile.cc207
-rw-r--r--runtime/trace_profile.h74
-rw-r--r--tools/cpp-define-generator/thread.def2
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,