mirror of
https://github.com/chromium/crashpad.git
synced 2025-01-14 09:17:57 +08:00
linux: Collect CPU times in ProcStatReader and use in ProcessReader
Bug: crashpad:30 Change-Id: I6d4020220031670937acad12d0b7878c1ae0fae7 Reviewed-on: https://chromium-review.googlesource.com/583952 Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
90e4649f0d
commit
01b347732e
@ -26,6 +26,7 @@
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
#include "util/linux/proc_stat_reader.h"
|
||||
#include "util/posix/scoped_dir.h"
|
||||
|
||||
namespace crashpad {
|
||||
@ -210,6 +211,46 @@ bool ProcessReader::Initialize(pid_t pid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcessReader::StartTime(timeval* start_time) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return process_info_.StartTime(start_time);
|
||||
}
|
||||
|
||||
bool ProcessReader::CPUTimes(timeval* user_time, timeval* system_time) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
timerclear(user_time);
|
||||
timerclear(system_time);
|
||||
|
||||
timeval local_user_time;
|
||||
timerclear(&local_user_time);
|
||||
timeval local_system_time;
|
||||
timerclear(&local_system_time);
|
||||
|
||||
for (const Thread& thread : threads_) {
|
||||
ProcStatReader stat;
|
||||
if (!stat.Initialize(thread.tid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
timeval thread_user_time;
|
||||
if (!stat.UserCPUTime(&thread_user_time)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
timeval thread_system_time;
|
||||
if (!stat.SystemCPUTime(&thread_system_time)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
timeradd(&local_user_time, &thread_user_time, &local_user_time);
|
||||
timeradd(&local_system_time, &thread_system_time, &local_system_time);
|
||||
}
|
||||
|
||||
*user_time = local_user_time;
|
||||
*system_time = local_system_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<ProcessReader::Thread>& ProcessReader::Threads() {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
if (!initialized_threads_) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
#ifndef CRASHPAD_SNAPSHOT_LINUX_PROCESS_READER_H_
|
||||
#define CRASHPAD_SNAPSHOT_LINUX_PROCESS_READER_H_
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <memory>
|
||||
@ -83,6 +84,25 @@ class ProcessReader {
|
||||
//! \brief Return a memory map of the target process.
|
||||
MemoryMap* GetMemoryMap() { return &memory_map_; }
|
||||
|
||||
//! \brief Determines the target process’ start time.
|
||||
//!
|
||||
//! \param[out] start_time The time that the process started.
|
||||
//! \return `true` on success with \a start_time set. Otherwise `false` with a
|
||||
//! message logged.
|
||||
bool StartTime(timeval* start_time) const;
|
||||
|
||||
//! \brief Determines the target process’ execution time.
|
||||
//!
|
||||
//! \param[out] user_time The amount of time the process has executed code in
|
||||
//! user mode.
|
||||
//! \param[out] system_time The amount of time the process has executed code
|
||||
//! in system mode.
|
||||
//!
|
||||
//! \return `true` on success, `false` on failure, with a warning logged. On
|
||||
//! failure, \a user_time and \a system_time will be set to represent no
|
||||
//! time spent executing code in user or system mode.
|
||||
bool CPUTimes(timeval* user_time, timeval* system_time) const;
|
||||
|
||||
//! \brief Return a vector of threads that are in the task process. If the
|
||||
//! main thread is able to be identified and traced, it will be placed at
|
||||
//! index `0`.
|
||||
|
@ -43,67 +43,66 @@ void TimespecToTimeval(const timespec& ts, timeval* tv) {
|
||||
tv->tv_usec = ts.tv_nsec / 1000;
|
||||
}
|
||||
|
||||
long GetClockTicksPerSecond() {
|
||||
long clock_ticks_per_s = sysconf(_SC_CLK_TCK);
|
||||
if (clock_ticks_per_s <= 0) {
|
||||
PLOG(ERROR) << "sysconf";
|
||||
}
|
||||
return clock_ticks_per_s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProcStatReader::ProcStatReader() : tid_(-1) {}
|
||||
ProcStatReader::ProcStatReader()
|
||||
: contents_(), third_column_position_(0), initialized_() {}
|
||||
|
||||
ProcStatReader::~ProcStatReader() {}
|
||||
|
||||
bool ProcStatReader::Initialize(pid_t tid) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
// This might do more in the future.
|
||||
tid_ = tid;
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcStatReader::StartTime(timeval* start_time) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
std::string stat_contents;
|
||||
if (!ReadFile(&stat_contents)) {
|
||||
if (!ReadFile(tid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The process start time is the 22nd column.
|
||||
// The second column is the executable name in parentheses.
|
||||
// The first column is process ID and the second column is the executable name
|
||||
// in parentheses. This class only cares about columns after the second, so
|
||||
// find the start of the third here and save it for later.
|
||||
// The executable name may have parentheses itself, so find the end of the
|
||||
// second column by working backwards to find the last closing parens and
|
||||
// then count forward to the 22nd column.
|
||||
size_t stat_pos = stat_contents.rfind(')');
|
||||
// second column by working backwards to find the last closing parens.
|
||||
size_t stat_pos = contents_.rfind(')');
|
||||
if (stat_pos == std::string::npos) {
|
||||
LOG(ERROR) << "format error";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 1; index < 21; ++index) {
|
||||
stat_pos = stat_contents.find(' ', stat_pos);
|
||||
if (stat_pos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
++stat_pos;
|
||||
}
|
||||
if (stat_pos >= stat_contents.size()) {
|
||||
third_column_position_ = contents_.find(' ', stat_pos);
|
||||
if (third_column_position_ == std::string::npos ||
|
||||
++third_column_position_ >= contents_.size()) {
|
||||
LOG(ERROR) << "format error";
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* ticks_ptr = &stat_contents[stat_pos];
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcStatReader::UserCPUTime(timeval* user_time) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return ReadTimeAtIndex(13, user_time);
|
||||
}
|
||||
|
||||
bool ProcStatReader::SystemCPUTime(timeval* system_time) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return ReadTimeAtIndex(14, system_time);
|
||||
}
|
||||
|
||||
bool ProcStatReader::StartTime(timeval* start_time) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
// start time is in jiffies instead of clock ticks pre 2.6.
|
||||
uint64_t ticks_after_boot;
|
||||
if (!AdvancePastNumber<uint64_t>(&ticks_ptr, &ticks_after_boot)) {
|
||||
LOG(ERROR) << "format error";
|
||||
return false;
|
||||
}
|
||||
long clock_ticks_per_s = sysconf(_SC_CLK_TCK);
|
||||
if (clock_ticks_per_s <= 0) {
|
||||
PLOG(ERROR) << "sysconf";
|
||||
return false;
|
||||
}
|
||||
timeval time_after_boot;
|
||||
time_after_boot.tv_sec = ticks_after_boot / clock_ticks_per_s;
|
||||
time_after_boot.tv_usec = (ticks_after_boot % clock_ticks_per_s) *
|
||||
(static_cast<long>(1E6) / clock_ticks_per_s);
|
||||
if (!ReadTimeAtIndex(21, &time_after_boot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
timespec uptime;
|
||||
if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) {
|
||||
@ -126,13 +125,53 @@ bool ProcStatReader::StartTime(timeval* start_time) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcStatReader::ReadFile(std::string* contents) const {
|
||||
bool ProcStatReader::ReadFile(pid_t tid) {
|
||||
char path[32];
|
||||
snprintf(path, arraysize(path), "/proc/%d/stat", tid_);
|
||||
if (!LoggingReadEntireFile(base::FilePath(path), contents)) {
|
||||
snprintf(path, arraysize(path), "/proc/%d/stat", tid);
|
||||
if (!LoggingReadEntireFile(base::FilePath(path), &contents_)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcStatReader::FindColumn(int col_index, const char** column) const {
|
||||
size_t position = third_column_position_;
|
||||
for (int index = 2; index < col_index; ++index) {
|
||||
position = contents_.find(' ', position);
|
||||
if (position == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
++position;
|
||||
}
|
||||
if (position >= contents_.size()) {
|
||||
LOG(ERROR) << "format error";
|
||||
return false;
|
||||
}
|
||||
*column = &contents_[position];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcStatReader::ReadTimeAtIndex(int index, timeval* time_val) const {
|
||||
const char* ticks_ptr;
|
||||
if (!FindColumn(index, &ticks_ptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t ticks;
|
||||
if (!AdvancePastNumber<uint64_t>(&ticks_ptr, &ticks)) {
|
||||
LOG(ERROR) << "format error";
|
||||
return false;
|
||||
}
|
||||
|
||||
static long clock_ticks_per_s = GetClockTicksPerSecond();
|
||||
if (clock_ticks_per_s <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
time_val->tv_sec = ticks / clock_ticks_per_s;
|
||||
time_val->tv_usec = (ticks % clock_ticks_per_s) *
|
||||
(static_cast<long>(1E6) / clock_ticks_per_s);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
||||
|
@ -15,6 +15,7 @@
|
||||
#ifndef CRASHPAD_UTIL_LINUX_PROC_STAT_READER_H_
|
||||
#define CRASHPAD_UTIL_LINUX_PROC_STAT_READER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
@ -38,6 +39,22 @@ class ProcStatReader {
|
||||
//! \param[in] tid The thread ID to read the stat file for.
|
||||
bool Initialize(pid_t tid);
|
||||
|
||||
//! \brief Determines the time the thread has spent executing in user mode.
|
||||
//!
|
||||
//! \param[out] user_time The time spent executing in user mode.
|
||||
//!
|
||||
//! \return `true` on success, with \a user_time set. Otherwise, `false` with
|
||||
//! a message logged.
|
||||
bool UserCPUTime(timeval* user_time) const;
|
||||
|
||||
//! \brief Determines the time the thread has spent executing in system mode.
|
||||
//!
|
||||
//! \param[out] system_time The time spent executing in system mode.
|
||||
//!
|
||||
//! \return `true` on success, with \a system_time set. Otherwise, `false`
|
||||
//! with a message logged.
|
||||
bool SystemCPUTime(timeval* system_time) const;
|
||||
|
||||
//! \brief Determines the target thread’s start time.
|
||||
//!
|
||||
//! \param[out] start_time The time that the thread started.
|
||||
@ -47,9 +64,12 @@ class ProcStatReader {
|
||||
bool StartTime(timeval* start_time) const;
|
||||
|
||||
private:
|
||||
bool ReadFile(std::string* contents) const;
|
||||
bool ReadFile(pid_t tid);
|
||||
bool FindColumn(int index, const char** column) const;
|
||||
bool ReadTimeAtIndex(int index, timeval* time_val) const;
|
||||
|
||||
pid_t tid_;
|
||||
std::string contents_;
|
||||
size_t third_column_position_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcStatReader);
|
||||
|
@ -36,6 +36,16 @@ TEST(ProcStatReader, Basic) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
EXPECT_LE(start_time.tv_sec, now);
|
||||
|
||||
time_t elapsed_sec = now - start_time.tv_sec;
|
||||
|
||||
timeval user_time;
|
||||
ASSERT_TRUE(stat.UserCPUTime(&user_time));
|
||||
EXPECT_LE(user_time.tv_sec, elapsed_sec);
|
||||
|
||||
timeval system_time;
|
||||
ASSERT_TRUE(stat.SystemCPUTime(&system_time));
|
||||
EXPECT_LE(system_time.tv_sec, elapsed_sec);
|
||||
}
|
||||
|
||||
pid_t gettid() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user