0
Files
src/base/linux_util.cc
Alexandr Ilin d4f4b34f77 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}
2019-01-08 15:34:09 +00:00

228 lines
6.4 KiB
C++

// Copyright (c) 2012 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/linux_util.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#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"
#include "base/strings/string_util.h"
#include "base/synchronization/lock.h"
#include "build/build_config.h"
namespace {
// Not needed for OS_CHROMEOS.
#if defined(OS_LINUX)
enum LinuxDistroState {
STATE_DID_NOT_CHECK = 0,
STATE_CHECK_STARTED = 1,
STATE_CHECK_FINISHED = 2,
};
// Helper class for GetLinuxDistro().
class LinuxDistroHelper {
public:
// Retrieves the Singleton.
static LinuxDistroHelper* GetInstance() {
return base::Singleton<LinuxDistroHelper>::get();
}
// The simple state machine goes from:
// STATE_DID_NOT_CHECK -> STATE_CHECK_STARTED -> STATE_CHECK_FINISHED.
LinuxDistroHelper() : state_(STATE_DID_NOT_CHECK) {}
~LinuxDistroHelper() = default;
// Retrieve the current state, if we're in STATE_DID_NOT_CHECK,
// we automatically move to STATE_CHECK_STARTED so nobody else will
// do the check.
LinuxDistroState State() {
base::AutoLock scoped_lock(lock_);
if (STATE_DID_NOT_CHECK == state_) {
state_ = STATE_CHECK_STARTED;
return STATE_DID_NOT_CHECK;
}
return state_;
}
// Indicate the check finished, move to STATE_CHECK_FINISHED.
void CheckFinished() {
base::AutoLock scoped_lock(lock_);
DCHECK_EQ(STATE_CHECK_STARTED, state_);
state_ = STATE_CHECK_FINISHED;
}
private:
base::Lock lock_;
LinuxDistroState state_;
};
#endif // if defined(OS_LINUX)
} // namespace
namespace base {
// Account for the terminating null character.
static const int kDistroSize = 128 + 1;
// We use this static string to hold the Linux distro info. If we
// crash, the crash handler code will send this in the crash dump.
char g_linux_distro[kDistroSize] =
#if defined(OS_CHROMEOS)
"CrOS";
#elif defined(OS_ANDROID)
"Android";
#else // if defined(OS_LINUX)
"Unknown";
#endif
std::string GetLinuxDistro() {
#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
return g_linux_distro;
#elif defined(OS_LINUX)
LinuxDistroHelper* distro_state_singleton = LinuxDistroHelper::GetInstance();
LinuxDistroState state = distro_state_singleton->State();
if (STATE_CHECK_FINISHED == state)
return g_linux_distro;
if (STATE_CHECK_STARTED == state)
return "Unknown"; // Don't wait for other thread to finish.
DCHECK_EQ(state, STATE_DID_NOT_CHECK);
// We do this check only once per process. If it fails, there's
// little reason to believe it will work if we attempt to run
// lsb_release again.
std::vector<std::string> argv;
argv.push_back("lsb_release");
argv.push_back("-d");
std::string output;
GetAppOutput(CommandLine(argv), &output);
if (output.length() > 0) {
// lsb_release -d should return: Description:<tab>Distro Info
const char field[] = "Description:\t";
if (output.compare(0, strlen(field), field) == 0) {
SetLinuxDistro(output.substr(strlen(field)));
}
}
distro_state_singleton->CheckFinished();
return g_linux_distro;
#else
NOTIMPLEMENTED();
return "Unknown";
#endif
}
void SetLinuxDistro(const std::string& distro) {
std::string trimmed_distro;
TrimWhitespaceASCII(distro, TRIM_ALL, &trimmed_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 (!GetThreadsForProcess(pid, &tids))
return -1;
std::unique_ptr<char[]> syscall_data(new char[expected_data.length()]);
for (pid_t tid : tids) {
char buf[256];
snprintf(buf, sizeof(buf), "/proc/%d/task/%d/syscall", pid, tid);
int fd = open(buf, O_RDONLY);
if (fd < 0)
continue;
if (syscall_supported != nullptr)
*syscall_supported = true;
bool read_ret = ReadFromFD(fd, syscall_data.get(), expected_data.length());
close(fd);
if (!read_ret)
continue;
if (0 == strncmp(expected_data.c_str(), syscall_data.get(),
expected_data.length())) {
return tid;
}
}
return -1;
}
pid_t FindThreadID(pid_t pid, pid_t ns_tid, bool* ns_pid_supported) {
if (ns_pid_supported)
*ns_pid_supported = false;
std::vector<pid_t> tids;
if (!GetThreadsForProcess(pid, &tids))
return -1;
for (pid_t tid : tids) {
char buf[256];
snprintf(buf, sizeof(buf), "/proc/%d/task/%d/status", pid, tid);
std::string status;
if (!ReadFileToString(FilePath(buf), &status))
return -1;
StringTokenizer tokenizer(status, "\n");
while (tokenizer.GetNext()) {
StringPiece value_str(tokenizer.token_piece());
if (!value_str.starts_with("NSpid"))
continue;
if (ns_pid_supported)
*ns_pid_supported = true;
std::vector<StringPiece> split_value_str = SplitStringPiece(
value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
DCHECK_GE(split_value_str.size(), 2u);
int value;
// The last value in the list is the PID in the namespace.
if (StringToInt(split_value_str.back(), &value) && value == ns_tid) {
// The second value in the list is the real PID.
if (StringToInt(split_value_str[1], &value))
return value;
}
break;
}
}
return -1;
}
} // namespace base