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:
Joshua Peraza 2017-07-24 18:21:22 -07:00
parent 90e4649f0d
commit 01b347732e
5 changed files with 174 additions and 44 deletions

View File

@ -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_) {

View File

@ -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`.

View File

@ -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

View File

@ -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 threads 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);

View File

@ -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() {