summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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,