android: Reached code sampling profiler.
Reached code profiler periodically interrupts threads in all Chrome processes and stores process counter values as array of offsets. Later, this array may be dumped to the disk or uploaded to a server for further analysis. In order to collect samples this CL does following: - Registers a signal handler early in the process creation. This handler writes the current pc value into array. A signal handler is a process-global attribute shared by all threads so every thread is able to handle a signal. - Sets up an interval timer (via timer_create()) measuring CPU time consumed by the calling process. This timer periodically calls SendSignalToAllThreads() on a dedicated thread. - SendSignalToAllThreads() has a cached list of all thread ids belonging to the current process obtained by parsing /proc/self/task directory. It sends a signal to all listed threads via tgkill() and periodically updates this list. This allows to collect ~6000 samples per seconds from all threads. This CL is based on lizeb@ work: https://crrev.com/c/1171233 Bug: 916263 Change-Id: I661ca6d65be694053458e2a6afa1dab62fd9812c Reviewed-on: https://chromium-review.googlesource.com/c/1319597 Commit-Queue: Alexandr Ilin <alexilin@chromium.org> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Reviewed-by: Yaron Friedman <yfriedman@chromium.org> Reviewed-by: ssid <ssid@chromium.org> Reviewed-by: Egor Pasko <pasko@chromium.org> Cr-Commit-Position: refs/heads/master@{#620731}
This commit is contained in:

committed by
Commit Bot

parent
7dbc322d58
commit
d4f4b34f77
@ -1313,6 +1313,8 @@ jumbo_component("base") {
|
||||
"android/path_service_android.cc",
|
||||
"android/path_utils.cc",
|
||||
"android/path_utils.h",
|
||||
"android/reached_code_profiler.cc",
|
||||
"android/reached_code_profiler.h",
|
||||
"android/record_histogram.cc",
|
||||
"android/record_user_action.cc",
|
||||
"android/scoped_hardware_buffer_handle.cc",
|
||||
@ -1382,6 +1384,13 @@ jumbo_component("base") {
|
||||
]
|
||||
}
|
||||
|
||||
if (current_cpu != "arm") {
|
||||
# The reached code profiler is only supported on Android with 32-bit arm
|
||||
# arch.
|
||||
sources -= [ "android/reached_code_profiler.cc" ]
|
||||
sources += [ "android/reached_code_profiler_stub.cc" ]
|
||||
}
|
||||
|
||||
# This is actually a linker script, but it can be added to the link in the
|
||||
# same way as a library.
|
||||
libs += [ "android/library_loader/anchor_functions.lds" ]
|
||||
|
326
base/android/reached_code_profiler.cc
Normal file
326
base/android/reached_code_profiler.cc
Normal file
@ -0,0 +1,326 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/android/reached_code_profiler.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "base/android/library_loader/anchor_functions.h"
|
||||
#include "base/android/orderfile/orderfile_buildflags.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/important_file_writer.h"
|
||||
#include "base/linux_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/optional.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/scoped_generic.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "base/timer/timer.h"
|
||||
|
||||
#if !BUILDFLAG(SUPPORTS_CODE_ORDERING)
|
||||
#error Code ordering support is required for the reached code profiler.
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
namespace android {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char kDumpToFileFlag[] = "reached-code-profiler-dump-to-file";
|
||||
|
||||
// Enough for 1 << 29 bytes of code, 512MB.
|
||||
constexpr size_t kBitfieldSize = 1 << 20;
|
||||
constexpr size_t kBitsPerElement = 4 * 32;
|
||||
|
||||
constexpr uint64_t kIterationsBeforeSkipping = 50;
|
||||
constexpr uint64_t kIterationsBetweenUpdates = 100;
|
||||
constexpr int kProfilerSignal = SIGURG;
|
||||
|
||||
constexpr base::TimeDelta kSamplingInterval =
|
||||
base::TimeDelta::FromMilliseconds(10);
|
||||
constexpr base::TimeDelta kDumpInterval = base::TimeDelta::FromSeconds(30);
|
||||
|
||||
std::atomic<uint32_t> g_reached[kBitfieldSize];
|
||||
std::atomic<std::atomic<uint32_t>*> g_enabled_and_reached(g_reached);
|
||||
|
||||
size_t NumberOfReachableElements() {
|
||||
return (kEndOfText - kStartOfText) / kBitsPerElement + 1;
|
||||
}
|
||||
|
||||
void RecordAddress(uint32_t address) {
|
||||
auto* reached = g_enabled_and_reached.load(std::memory_order_relaxed);
|
||||
if (!reached)
|
||||
return;
|
||||
|
||||
// Stopped in libc, third-party, or Java code.
|
||||
if (address < kStartOfText || address > kEndOfText)
|
||||
return;
|
||||
|
||||
size_t offset = address - kStartOfText;
|
||||
static_assert(sizeof(int) == 4,
|
||||
"Collection and processing code assumes that sizeof(int) == 4");
|
||||
size_t offset_index = offset / 4;
|
||||
|
||||
// Atomically set the corresponding bit in the array.
|
||||
std::atomic<uint32_t>* element = reached + (offset_index / 32);
|
||||
// First, a racy check. This saves a CAS if the bit is already set, and
|
||||
// allows the cache line to remain shared acoss CPUs in this case.
|
||||
uint32_t value = element->load(std::memory_order_relaxed);
|
||||
uint32_t mask = 1 << (offset_index % 32);
|
||||
if (value & mask)
|
||||
return;
|
||||
element->fetch_or(mask, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void HandleSignal(int signal, siginfo_t* info, void* context) {
|
||||
if (signal != kProfilerSignal)
|
||||
return;
|
||||
|
||||
ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context);
|
||||
uint32_t address = ucontext->uc_mcontext.arm_pc;
|
||||
RecordAddress(address);
|
||||
}
|
||||
|
||||
struct ScopedTimerCloseTraits {
|
||||
static base::Optional<timer_t> InvalidValue() { return base::nullopt; }
|
||||
|
||||
static void Free(base::Optional<timer_t> x) { timer_delete(*x); }
|
||||
};
|
||||
|
||||
// RAII object holding an interval timer.
|
||||
using ScopedTimer =
|
||||
base::ScopedGeneric<base::Optional<timer_t>, ScopedTimerCloseTraits>;
|
||||
|
||||
std::vector<uint8_t> SnapshotReachedCodeBitset() {
|
||||
std::vector<uint8_t> buf;
|
||||
size_t elements = NumberOfReachableElements();
|
||||
buf.resize(elements * sizeof(uint32_t));
|
||||
// Copy the reached array into a buffer with atomic loads with the explicit
|
||||
// memory ordering flag. In practice this is likely not necessary because:
|
||||
// a) integrity of the data across individual elements of |g_reached| is not
|
||||
// maintained anyway
|
||||
// b) write(2) will not take the data in smaller chunks than 4 bytes
|
||||
// c) it would be bizarre for mojo initialization code to cause the compiler
|
||||
// to spill stuff into the array..
|
||||
// Anyway .. come to the Safe Side, we have CPUs to spin.
|
||||
for (size_t i = 0; i < elements; i++) {
|
||||
uint32_t word = g_reached[i].load(std::memory_order_relaxed);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
buf[4 * i + j] = static_cast<uint8_t>((word >> (j * 8)) & 0xFF);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
void DumpToFile(const base::FilePath& path,
|
||||
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
|
||||
CHECK(task_runner->BelongsToCurrentThread());
|
||||
|
||||
auto dir_path = path.DirName();
|
||||
if (!base::DirectoryExists(dir_path) && !base::CreateDirectory(dir_path)) {
|
||||
PLOG(ERROR) << "Could not create " << dir_path;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buf = SnapshotReachedCodeBitset();
|
||||
base::StringPiece contents(reinterpret_cast<const char*>(buf.data()),
|
||||
buf.size());
|
||||
if (!base::ImportantFileWriter::WriteFileAtomically(path, contents,
|
||||
"ReachedDump")) {
|
||||
LOG(ERROR) << "Could not write reached dump into " << path;
|
||||
}
|
||||
|
||||
task_runner->PostDelayedTask(
|
||||
FROM_HERE, base::BindOnce(&DumpToFile, path, task_runner), kDumpInterval);
|
||||
}
|
||||
|
||||
class ReachedCodeProfiler {
|
||||
public:
|
||||
static ReachedCodeProfiler* GetInstance() {
|
||||
static base::NoDestructor<ReachedCodeProfiler> instance;
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
// Starts to periodically send |kProfilerSignal| to all threads.
|
||||
void Start(LibraryProcessType library_process_type) {
|
||||
if (is_enabled_)
|
||||
return;
|
||||
|
||||
// Set |kProfilerSignal| signal handler.
|
||||
// TODO(crbug.com/916263): consider restoring |old_handler| after the
|
||||
// profiler gets stopped.
|
||||
struct sigaction old_handler;
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_sigaction = &HandleSignal;
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
int ret = sigaction(kProfilerSignal, &sa, &old_handler);
|
||||
if (ret) {
|
||||
PLOG(ERROR) << "Error setting signal handler. The reached code profiler "
|
||||
"is disabled";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new interval timer.
|
||||
struct sigevent sevp;
|
||||
memset(&sevp, 0, sizeof(sevp));
|
||||
sevp.sigev_notify = SIGEV_THREAD;
|
||||
sevp.sigev_notify_function = &OnTimerNotify;
|
||||
timer_t timerid;
|
||||
ret = timer_create(CLOCK_PROCESS_CPUTIME_ID, &sevp, &timerid);
|
||||
if (ret) {
|
||||
PLOG(ERROR)
|
||||
<< "timer_create() failed. The reached code profiler is disabled";
|
||||
return;
|
||||
}
|
||||
|
||||
timer_.reset(timerid);
|
||||
|
||||
// Start the interval timer.
|
||||
struct itimerspec its;
|
||||
memset(&its, 0, sizeof(its));
|
||||
its.it_interval.tv_nsec = kSamplingInterval.InNanoseconds();
|
||||
its.it_value = its.it_interval;
|
||||
ret = timer_settime(timerid, 0, &its, nullptr);
|
||||
if (ret) {
|
||||
PLOG(ERROR)
|
||||
<< "timer_settime() failed. The reached code profiler is disabled";
|
||||
return;
|
||||
}
|
||||
|
||||
if (library_process_type == PROCESS_BROWSER)
|
||||
StartDumpingReachedCode();
|
||||
|
||||
is_enabled_ = true;
|
||||
}
|
||||
|
||||
// Stops profiling.
|
||||
void Stop() {
|
||||
timer_.reset();
|
||||
dumping_thread_.reset();
|
||||
is_enabled_ = false;
|
||||
}
|
||||
|
||||
// Returns whether the profiler is currently enabled.
|
||||
bool IsEnabled() { return is_enabled_; }
|
||||
|
||||
private:
|
||||
ReachedCodeProfiler()
|
||||
: current_pid_(getpid()), iteration_number_(0), is_enabled_(false) {}
|
||||
|
||||
static void OnTimerNotify(sigval_t ignored) {
|
||||
ReachedCodeProfiler::GetInstance()->SendSignalToAllThreads();
|
||||
}
|
||||
|
||||
void SendSignalToAllThreads() {
|
||||
// This code should be thread-safe.
|
||||
base::AutoLock scoped_lock(lock_);
|
||||
++iteration_number_;
|
||||
|
||||
if (iteration_number_ <= kIterationsBeforeSkipping ||
|
||||
iteration_number_ % kIterationsBetweenUpdates == 0) {
|
||||
tids_.clear();
|
||||
if (!base::GetThreadsForProcess(current_pid_, &tids_)) {
|
||||
LOG(WARNING) << "Failed to get a list of threads for process "
|
||||
<< current_pid_;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pid_t current_tid = gettid();
|
||||
for (pid_t tid : tids_) {
|
||||
if (tid != current_tid)
|
||||
tgkill(current_pid_, tid, kProfilerSignal);
|
||||
}
|
||||
}
|
||||
|
||||
void StartDumpingReachedCode() {
|
||||
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
|
||||
if (!cmdline->HasSwitch(kDumpToFileFlag))
|
||||
return;
|
||||
|
||||
base::FilePath dir_path(cmdline->GetSwitchValueASCII(kDumpToFileFlag));
|
||||
if (dir_path.empty()) {
|
||||
if (!base::PathService::Get(base::DIR_CACHE, &dir_path)) {
|
||||
LOG(WARNING) << "Failed to get cache dir path.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto file_path =
|
||||
dir_path.Append(base::StringPrintf("reached-code-%d.txt", getpid()));
|
||||
|
||||
dumping_thread_ =
|
||||
std::make_unique<base::Thread>("ReachedCodeProfilerDumpingThread");
|
||||
base::Thread::Options options;
|
||||
options.priority = base::ThreadPriority::BACKGROUND;
|
||||
dumping_thread_->StartWithOptions(options);
|
||||
dumping_thread_->task_runner()->PostDelayedTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&DumpToFile, file_path, dumping_thread_->task_runner()),
|
||||
kDumpInterval);
|
||||
}
|
||||
|
||||
base::Lock lock_;
|
||||
std::vector<pid_t> tids_;
|
||||
const pid_t current_pid_;
|
||||
uint64_t iteration_number_;
|
||||
ScopedTimer timer_;
|
||||
std::unique_ptr<base::Thread> dumping_thread_;
|
||||
|
||||
bool is_enabled_;
|
||||
|
||||
friend class NoDestructor<ReachedCodeProfiler>;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ReachedCodeProfiler);
|
||||
};
|
||||
|
||||
bool ShouldEnableReachedCodeProfiler() {
|
||||
#if !defined(NDEBUG) || defined(COMPONENT_BUILD)
|
||||
// Always disabled for debug builds to avoid hitting a limit of signal
|
||||
// interrupts that can get delivered into a single HANDLE_EINTR. Also
|
||||
// debugging experience would be bad if there are a lot of signals flying
|
||||
// around.
|
||||
// Always disabled for component builds because in this case the code is not
|
||||
// organized in one contiguous region which is required for the reached code
|
||||
// profiler.
|
||||
return false;
|
||||
#else
|
||||
// TODO(crbug.com/916263): this should be set up according to the finch
|
||||
// experiment.
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitReachedCodeProfilerAtStartup(LibraryProcessType library_process_type) {
|
||||
// The profiler shouldn't be run as part of webview.
|
||||
CHECK(library_process_type == PROCESS_BROWSER ||
|
||||
library_process_type == PROCESS_CHILD);
|
||||
|
||||
if (!ShouldEnableReachedCodeProfiler())
|
||||
return;
|
||||
|
||||
ReachedCodeProfiler::GetInstance()->Start(library_process_type);
|
||||
}
|
||||
|
||||
bool IsReachedCodeProfilerEnabled() {
|
||||
return ReachedCodeProfiler::GetInstance()->IsEnabled();
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
} // namespace base
|
30
base/android/reached_code_profiler.h
Normal file
30
base/android/reached_code_profiler.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_ANDROID_REACHED_CODE_PROFILER_H_
|
||||
#define BASE_ANDROID_REACHED_CODE_PROFILER_H_
|
||||
|
||||
#include "base/android/library_loader/library_loader_hooks.h"
|
||||
#include "base/base_export.h"
|
||||
|
||||
namespace base {
|
||||
namespace android {
|
||||
|
||||
// Initializes and starts the reached code profiler for |library_process_type|.
|
||||
// Reached symbols are not recorded before calling this function, so it has to
|
||||
// be called as early in startup as possible. This has to be called before the
|
||||
// process creates any thread.
|
||||
// TODO(crbug.com/916263): Currently, the reached code profiler must be
|
||||
// initialized before the tracing profiler. If we want to start it at later
|
||||
// point, we need to check that the tracing profiler isn't initialized first.
|
||||
BASE_EXPORT void InitReachedCodeProfilerAtStartup(
|
||||
LibraryProcessType library_process_type);
|
||||
|
||||
// Returns whether the reached code profiler is enabled.
|
||||
BASE_EXPORT bool IsReachedCodeProfilerEnabled();
|
||||
|
||||
} // namespace android
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_ANDROID_REACHED_CODE_PROFILER_H_
|
18
base/android/reached_code_profiler_stub.cc
Normal file
18
base/android/reached_code_profiler_stub.cc
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/android/reached_code_profiler.h"
|
||||
|
||||
namespace base {
|
||||
namespace android {
|
||||
|
||||
void InitReachedCodeProfilerAtStartup(LibraryProcessType library_process_type) {
|
||||
}
|
||||
|
||||
bool IsReachedCodeProfilerEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
} // namespace base
|
@ -14,12 +14,13 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/dir_reader_posix.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/process/launch.h"
|
||||
#include "base/strings/safe_sprintf.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/string_tokenizer.h"
|
||||
@ -75,28 +76,6 @@ class LinuxDistroHelper {
|
||||
};
|
||||
#endif // if defined(OS_LINUX)
|
||||
|
||||
bool GetTasksForProcess(pid_t pid, std::vector<pid_t>* tids) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
|
||||
|
||||
DIR* task = opendir(buf);
|
||||
if (!task) {
|
||||
DLOG(WARNING) << "Cannot open " << buf;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dirent* dent;
|
||||
while ((dent = readdir(task))) {
|
||||
char* endptr;
|
||||
const unsigned long int tid_ul = strtoul(dent->d_name, &endptr, 10);
|
||||
if (tid_ul == ULONG_MAX || *endptr)
|
||||
continue;
|
||||
tids->push_back(tid_ul);
|
||||
}
|
||||
closedir(task);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
@ -155,13 +134,35 @@ void SetLinuxDistro(const std::string& distro) {
|
||||
strlcpy(g_linux_distro, trimmed_distro.c_str(), kDistroSize);
|
||||
}
|
||||
|
||||
bool GetThreadsForProcess(pid_t pid, std::vector<pid_t>* tids) {
|
||||
// 25 > strlen("/proc//task") + strlen(std::to_string(INT_MAX)) + 1 = 22
|
||||
char buf[25];
|
||||
base::strings::SafeSPrintf(buf, "/proc/%d/task", pid);
|
||||
DirReaderPosix dir_reader(buf);
|
||||
|
||||
if (!dir_reader.IsValid()) {
|
||||
DLOG(WARNING) << "Cannot open " << buf;
|
||||
return false;
|
||||
}
|
||||
|
||||
while (dir_reader.Next()) {
|
||||
char* endptr;
|
||||
const unsigned long int tid_ul = strtoul(dir_reader.name(), &endptr, 10);
|
||||
if (tid_ul == ULONG_MAX || *endptr)
|
||||
continue;
|
||||
tids->push_back(tid_ul);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pid_t FindThreadIDWithSyscall(pid_t pid, const std::string& expected_data,
|
||||
bool* syscall_supported) {
|
||||
if (syscall_supported != nullptr)
|
||||
*syscall_supported = false;
|
||||
|
||||
std::vector<pid_t> tids;
|
||||
if (!GetTasksForProcess(pid, &tids))
|
||||
if (!GetThreadsForProcess(pid, &tids))
|
||||
return -1;
|
||||
|
||||
std::unique_ptr<char[]> syscall_data(new char[expected_data.length()]);
|
||||
@ -191,7 +192,7 @@ pid_t FindThreadID(pid_t pid, pid_t ns_tid, bool* ns_pid_supported) {
|
||||
*ns_pid_supported = false;
|
||||
|
||||
std::vector<pid_t> tids;
|
||||
if (!GetTasksForProcess(pid, &tids))
|
||||
if (!GetThreadsForProcess(pid, &tids))
|
||||
return -1;
|
||||
|
||||
for (pid_t tid : tids) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/base_export.h"
|
||||
|
||||
@ -24,6 +25,10 @@ BASE_EXPORT std::string GetLinuxDistro();
|
||||
// Set the Linux Distro string.
|
||||
BASE_EXPORT void SetLinuxDistro(const std::string& distro);
|
||||
|
||||
// For a given process |pid|, get a list of all its threads. On success, returns
|
||||
// true and appends the list of threads to |tids|. Otherwise, returns false.
|
||||
BASE_EXPORT bool GetThreadsForProcess(pid_t pid, std::vector<pid_t>* tids);
|
||||
|
||||
// For a given process |pid|, look through all its threads and find the first
|
||||
// thread with /proc/[pid]/task/[thread_id]/syscall whose first N bytes matches
|
||||
// |expected_data|, where N is the length of |expected_data|.
|
||||
|
@ -18,6 +18,10 @@
|
||||
#include "build/build_config.h"
|
||||
#include "build/buildflag.h"
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#include "base/android/reached_code_profiler.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \
|
||||
defined(OFFICIAL_BUILD)
|
||||
#include <dlfcn.h>
|
||||
@ -178,6 +182,13 @@ void TracingSamplerProfiler::OnTraceLogEnabled() {
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
// The sampler profiler would conflict with the reached code profiler if they
|
||||
// run at the same time because they use the same signal to suspend threads.
|
||||
if (base::android::IsReachedCodeProfilerEnabled())
|
||||
return;
|
||||
#endif
|
||||
|
||||
base::StackSamplingProfiler::SamplingParams params;
|
||||
params.samples_per_profile = std::numeric_limits<int>::max();
|
||||
params.sampling_interval = base::TimeDelta::FromMilliseconds(50);
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "content/app/android/library_loader_hooks.h"
|
||||
|
||||
#include "base/android/reached_code_profiler.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/trace_event/trace_event.h"
|
||||
#include "components/tracing/common/trace_startup.h"
|
||||
@ -14,6 +15,8 @@ namespace content {
|
||||
bool LibraryLoaded(JNIEnv* env,
|
||||
jclass clazz,
|
||||
base::android::LibraryProcessType library_process_type) {
|
||||
base::android::InitReachedCodeProfilerAtStartup(library_process_type);
|
||||
|
||||
// Enable startup tracing asap to avoid early TRACE_EVENT calls being ignored.
|
||||
tracing::EnableStartupTracingIfNeeded();
|
||||
|
||||
|
Reference in New Issue
Block a user