summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeungha Son <seungha.son@samsung.com>2020-04-01 10:35:13 +0900
committerSeungha Son <seungha.son@samsung.com>2020-04-08 15:05:54 +0900
commitac6ef8f388ce2062621ac639e77a9a3b04ae3402 (patch)
tree4728ddcd9fbf6ed80530b899785f75060ca4e321
parent016367713f8719af4c1c4578c8aa7d6215f678e3 (diff)
downloadttrace-ac6ef8f388ce2062621ac639e77a9a3b04ae3402.tar.gz
ttrace-ac6ef8f388ce2062621ac639e77a9a3b04ae3402.tar.bz2
ttrace-ac6ef8f388ce2062621ac639e77a9a3b04ae3402.zip
Add atrace-helper module
atrace_helper is an optional binary which can be pushed onto the device running systrace in order to enrich the traces with further details (memory, I/O, etc). - Memory snapshots of runnig processes(PSS/RSS) - Periodic snapshotting of processes and thread names. - File paths for filesystem events (only inode numbers). Base repo : https://chromium.googlesource.com/catapult/+/refs/heads/master/systrace/atrace_helper/ Changed contents : Remove specific logic related android Change-Id: I84b6dff914d4d5f151003e002dd0ff08ab0ae81c Signed-off-by: Seungha Son <seungha.son@samsung.com>
-rwxr-xr-xCMakeLists.txt24
-rw-r--r--packaging/ttrace.spec1
-rw-r--r--src/atrace_helper/atrace_process_dump.cc244
-rw-r--r--src/atrace_helper/atrace_process_dump.h81
-rw-r--r--src/atrace_helper/file_utils.cc117
-rw-r--r--src/atrace_helper/file_utils.h67
-rw-r--r--src/atrace_helper/logging.h32
-rw-r--r--src/atrace_helper/main.cc134
-rw-r--r--src/atrace_helper/process_info.h40
-rw-r--r--src/atrace_helper/process_memory_stats.cc124
-rw-r--r--src/atrace_helper/process_memory_stats.h72
-rw-r--r--src/atrace_helper/procfs_utils.cc137
-rw-r--r--src/atrace_helper/procfs_utils.h30
-rw-r--r--src/atrace_helper/time_utils.cc62
-rw-r--r--src/atrace_helper/time_utils.h34
15 files changed, 1199 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ca1c48f..1e82b2c 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -97,13 +97,37 @@ ENDFOREACH(flag)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CXXFLAGS} ${EXTRA_CXXFLAGS_common} -std=c++11")
MESSAGE("CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
+# Build atrace-helper
+SET(ATRACE_HELPER "atrace-helper")
+SET(ATRACE_HELPER_PATH src/atrace_helper)
+SET(SRCS_atrace_helper
+ ${ATRACE_HELPER_PATH}/atrace_process_dump.cc
+ ${ATRACE_HELPER_PATH}/file_utils.cc
+ ${ATRACE_HELPER_PATH}/main.cc
+ ${ATRACE_HELPER_PATH}/process_memory_stats.cc
+ ${ATRACE_HELPER_PATH}/procfs_utils.cc
+ ${ATRACE_HELPER_PATH}/time_utils.cc
+ )
+SET(HEADER_atrace_helper
+ ${ATRACE_HELPER_PATH}/atrace_process_dump.h
+ ${ATRACE_HELPER_PATH}/file_utils.h
+ ${ATRACE_HELPER_PATH}/logging.h
+ ${ATRACE_HELPER_PATH}/process_info.h
+ ${ATRACE_HELPER_PATH}/process_memory_stats.h
+ ${ATRACE_HELPER_PATH}/procfs_utils.h
+ ${ATRACE_HELPER_PATH}/tile_utils.h
+ )
+
ADD_EXECUTABLE(${ATRACE} ${SRCS_atrace})
SET_TARGET_PROPERTIES(${ATRACE} PROPERTIES SOVERSION ${VERSION_MAJOR})
SET_TARGET_PROPERTIES(${ATRACE} PROPERTIES VERSION ${VERSION})
#SET_TARGET_PROPERTIES(${ATRACE} PROPERTIES COMPILE_FLAGS ${EXTRA_CFLAGS_common})
TARGET_LINK_LIBRARIES(${ATRACE} ${pkg_atrace_LDFLAGS} "-ldl -lsmack")
+ADD_EXECUTABLE(${ATRACE_HELPER} ${SRCS_atrace_helper})
+
CONFIGURE_FILE(${ATRACE}.pc.in ${ATRACE}.pc @ONLY)
INSTALL(TARGETS ${ATRACE} DESTINATION bin)
+INSTALL(TARGETS ${ATRACE_HELPER} DESTINATION bin)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${ATRACE}.pc DESTINATION ${LIBDIR}/pkgconfig)
diff --git a/packaging/ttrace.spec b/packaging/ttrace.spec
index 5ee3a9e..d92e950 100644
--- a/packaging/ttrace.spec
+++ b/packaging/ttrace.spec
@@ -108,6 +108,7 @@ install -m 0644 gcov-obj/* %{buildroot}%{_datadir}/gcov/obj
%{_unitdir}/sys-kernel-debug-tracing.mount
%attr(755,root,users) %{_bindir}/atrace
%attr(755,root,users) %{_bindir}/atrace-1.1
+%attr(755,root,users) %{_bindir}/atrace-helper
%{_unitdir}/sysinit.target.wants/ttrace-marker.service
%{_unitdir}/sysinit.target.wants/sys-kernel-debug-tracing.mount
%attr(755,root,root) %{_bindir}/atrace-bootup.sh
diff --git a/src/atrace_helper/atrace_process_dump.cc b/src/atrace_helper/atrace_process_dump.cc
new file mode 100644
index 0000000..b6e8a29
--- /dev/null
+++ b/src/atrace_helper/atrace_process_dump.cc
@@ -0,0 +1,244 @@
+// Copyright 2017 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 "atrace_process_dump.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "file_utils.h"
+#include "logging.h"
+#include "procfs_utils.h"
+
+namespace {
+
+const int kMemInfoIntervalMs = 100; // 100ms-ish.
+
+} // namespace
+
+AtraceProcessDump::AtraceProcessDump() {
+ self_pid_ = static_cast<int>(getpid());
+}
+
+AtraceProcessDump::~AtraceProcessDump() {
+}
+
+void AtraceProcessDump::SetDumpInterval(int interval_ms) {
+ CHECK(interval_ms >= kMemInfoIntervalMs);
+ dump_interval_in_timer_ticks_ = interval_ms / kMemInfoIntervalMs;
+ // Approximately equals to kMemInfoIntervalMs.
+ int tick_interval_ms = interval_ms / dump_interval_in_timer_ticks_;
+ snapshot_timer_ = std::unique_ptr<time_utils::PeriodicTimer>(
+ new time_utils::PeriodicTimer(tick_interval_ms));
+}
+
+void AtraceProcessDump::RunAndPrintJson(FILE* stream) {
+ out_ = stream;
+
+ fprintf(out_, "{\"start_ts\": \"%" PRIu64 "\", \"snapshots\":[\n",
+ time_utils::GetTimestamp());
+
+ CHECK(snapshot_timer_);
+ snapshot_timer_->Start();
+
+ int tick_count = std::numeric_limits<int>::max();
+ if (dump_count_ > 0)
+ tick_count = dump_count_ * dump_interval_in_timer_ticks_;
+
+ for (int tick = 0; tick < tick_count; tick++) {
+ if (tick > 0) {
+ if (!snapshot_timer_->Wait())
+ break; // Interrupted by signal.
+ fprintf(out_, ",\n");
+ }
+ TakeAndSerializeMemInfo();
+ if (!(tick % dump_interval_in_timer_ticks_)) {
+ fprintf(out_, ",\n");
+ TakeGlobalSnapshot();
+ SerializeSnapshot();
+ }
+ fflush(out_);
+ }
+
+ fprintf(out_, "],\n");
+ SerializePersistentProcessInfo();
+ fprintf(out_, "}\n");
+ fflush(out_);
+ Cleanup();
+}
+
+void AtraceProcessDump::Stop() {
+ CHECK(snapshot_timer_);
+ snapshot_timer_->Stop();
+}
+
+void AtraceProcessDump::TakeGlobalSnapshot() {
+ snapshot_.clear();
+ snapshot_timestamp_ = time_utils::GetTimestamp();
+
+ file_utils::ForEachPidInProcPath("/proc", [this](int pid) {
+ // Skip if not regognized as a process.
+ if (!UpdatePersistentProcessInfo(pid))
+ return;
+ const ProcessInfo* process = processes_[pid].get();
+ // Snapshot can't be obtained for kernel workers.
+ if (process->in_kernel)
+ return;
+
+ ProcessSnapshot* process_snapshot = new ProcessSnapshot();
+ snapshot_[pid] = std::unique_ptr<ProcessSnapshot>(process_snapshot);
+
+ process_snapshot->pid = pid;
+ procfs_utils::ReadOomStats(process_snapshot);
+ procfs_utils::ReadPageFaultsAndCpuTimeStats(process_snapshot);
+
+ if (ShouldTakeFullDump(process)) {
+ process_snapshot->memory.ReadFullStats(pid);
+ } else {
+ process_snapshot->memory.ReadLightStats(pid);
+ }
+ });
+}
+
+bool AtraceProcessDump::UpdatePersistentProcessInfo(int pid) {
+ if (!processes_.count(pid)) {
+ if (procfs_utils::ReadTgid(pid) != pid)
+ return false;
+ processes_[pid] = procfs_utils::ReadProcessInfo(pid);
+ }
+ ProcessInfo* process = processes_[pid].get();
+ procfs_utils::ReadProcessThreads(process);
+
+ if (full_dump_mode_ == FullDumpMode::kOnlyWhitelisted &&
+ full_dump_whitelist_.count(process->name)) {
+ full_dump_whitelisted_pids_.insert(pid);
+ }
+ return true;
+}
+
+bool AtraceProcessDump::ShouldTakeFullDump(const ProcessInfo* process) {
+ if (full_dump_mode_ == FullDumpMode::kAllProcesses)
+ return !process->in_kernel && (process->pid != self_pid_);
+ if (full_dump_mode_ == FullDumpMode::kAllJavaApps)
+ return process->is_app;
+ if (full_dump_mode_ == FullDumpMode::kDisabled)
+ return false;
+ return full_dump_whitelisted_pids_.count(process->pid) > 0;
+}
+
+void AtraceProcessDump::SerializeSnapshot() {
+ fprintf(out_, "{\"ts\":\"%" PRIu64 "\",\"memdump\":{\n",
+ snapshot_timestamp_);
+ for (auto it = snapshot_.begin(); it != snapshot_.end();) {
+ const ProcessSnapshot* process = it->second.get();
+ const ProcessMemoryStats* mem = &process->memory;
+ fprintf(out_, "\"%d\":{", process->pid);
+
+ fprintf(out_, "\"vm\":%" PRIu64 ",\"rss\":%" PRIu64,
+ mem->virt_kb(), mem->rss_kb());
+
+ fprintf(out_, ",\"oom_sc\":%d,\"oom_sc_adj\":%d"
+ ",\"min_flt\":%lu,\"maj_flt\":%lu"
+ ",\"utime\":%lu,\"stime\":%lu",
+ process->oom_score, process->oom_score_adj,
+ process->minor_faults, process->major_faults,
+ process->utime, process->stime);
+
+ if (mem->full_stats_available()) {
+ fprintf(out_, ",\"pss\":%" PRIu64 ",\"swp\":%" PRIu64
+ ",\"pc\":%" PRIu64 ",\"pd\":%" PRIu64
+ ",\"sc\":%" PRIu64 ",\"sd\":%" PRIu64,
+ mem->pss_kb(), mem->swapped_kb(),
+ mem->private_clean_kb(), mem->private_dirty_kb(),
+ mem->shared_clean_kb(), mem->shared_dirty_kb());
+ }
+
+ // Memory maps are too heavy to serialize. Enable only in whitelisting mode.
+ if (print_smaps_ &&
+ full_dump_mode_ == FullDumpMode::kOnlyWhitelisted &&
+ mem->full_stats_available() &&
+ full_dump_whitelisted_pids_.count(process->pid)) {
+
+ fprintf(out_, ", \"mmaps\":[");
+ size_t n_mmaps = mem->mmaps_count();
+ for (size_t k = 0; k < n_mmaps; ++k) {
+ const ProcessMemoryStats::MmapInfo* mm = mem->mmap(k);
+ fprintf(out_,
+ "{\"vm\":\"%" PRIx64 "-%" PRIx64 "\","
+ "\"file\":\"%s\",\"flags\":\"%s\","
+ "\"pss\":%" PRIu64 ",\"rss\":%" PRIu64 ",\"swp\":%" PRIu64 ","
+ "\"pc\":%" PRIu64 ",\"pd\":%" PRIu64 ","
+ "\"sc\":%" PRIu64 ",\"sd\":%" PRIu64 "}",
+ mm->start_addr, mm->end_addr,
+ mm->mapped_file, mm->prot_flags,
+ mm->pss_kb, mm->rss_kb, mm->swapped_kb,
+ mm->private_clean_kb, mm->private_dirty_kb,
+ mm->shared_clean_kb, mm->shared_dirty_kb);
+ if (k < n_mmaps - 1)
+ fprintf(out_, ", ");
+ }
+ fprintf(out_, "]");
+ }
+
+ if (++it != snapshot_.end())
+ fprintf(out_, "},\n");
+ else
+ fprintf(out_, "}}\n");
+ }
+ fprintf(out_, "}");
+}
+
+void AtraceProcessDump::SerializePersistentProcessInfo() {
+ fprintf(out_, "\"processes\":{");
+ for (auto it = processes_.begin(); it != processes_.end();) {
+ const ProcessInfo* process = it->second.get();
+ fprintf(out_, "\"%d\":{", process->pid);
+ fprintf(out_, "\"name\":\"%s\"", process->name);
+
+ if (!process->in_kernel) {
+ fprintf(out_, ",\"exe\":\"%s\",", process->exe);
+ fprintf(out_, "\"threads\":{\n");
+ const auto threads = &process->threads;
+ for (auto thread_it = threads->begin(); thread_it != threads->end();) {
+ const ThreadInfo* thread = &(thread_it->second);
+ fprintf(out_, "\"%d\":{", thread->tid);
+ fprintf(out_, "\"name\":\"%s\"", thread->name);
+
+ if (++thread_it != threads->end())
+ fprintf(out_, "},\n");
+ else
+ fprintf(out_, "}\n");
+ }
+ fprintf(out_, "}");
+ }
+
+ if (++it != processes_.end())
+ fprintf(out_, "},\n");
+ else
+ fprintf(out_, "}\n");
+ }
+ fprintf(out_, "}");
+}
+
+void AtraceProcessDump::TakeAndSerializeMemInfo() {
+ std::map<std::string, uint64_t> mem_info;
+ CHECK(procfs_utils::ReadMemInfoStats(&mem_info));
+ fprintf(out_, "{\"ts\":\"%" PRIu64 "\",\"meminfo\":{\n",
+ time_utils::GetTimestamp());
+ for (auto it = mem_info.begin(); it != mem_info.end(); ++it) {
+ if (it != mem_info.begin())
+ fprintf(out_, ",");
+ fprintf(out_, "\"%s\":%" PRIu64, it->first.c_str(), it->second);
+ }
+ fprintf(out_, "}}");
+}
+
+void AtraceProcessDump::Cleanup() {
+ processes_.clear();
+ snapshot_.clear();
+ full_dump_whitelisted_pids_.clear();
+ snapshot_timer_ = nullptr;
+}
diff --git a/src/atrace_helper/atrace_process_dump.h b/src/atrace_helper/atrace_process_dump.h
new file mode 100644
index 0000000..280a18a
--- /dev/null
+++ b/src/atrace_helper/atrace_process_dump.h
@@ -0,0 +1,81 @@
+// Copyright 2017 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 ATRACE_PROCESS_DUMP_H_
+#define ATRACE_PROCESS_DUMP_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "logging.h"
+#include "process_info.h"
+#include "time_utils.h"
+
+// Program that collects processes, thread names, per-process memory stats and
+// other minor metrics from /proc filesystem. It's aimed to extend systrace
+// with more actionable number to hit performance issues.
+class AtraceProcessDump {
+ public:
+ enum FullDumpMode {
+ kDisabled,
+ kAllProcesses,
+ kAllJavaApps,
+ kOnlyWhitelisted,
+ };
+
+ AtraceProcessDump();
+ ~AtraceProcessDump();
+
+ void RunAndPrintJson(FILE* stream);
+ void Stop();
+
+ void SetDumpInterval(int interval_ms);
+
+ // Negative number or zero means unlimited number of dumps.
+ void set_dump_count(int count) { dump_count_ = count; }
+
+ void set_full_dump_mode(FullDumpMode mode) { full_dump_mode_ = mode; }
+ void set_full_dump_whitelist(const std::set<std::string> &whitelist) {
+ CHECK(full_dump_mode_ == FullDumpMode::kOnlyWhitelisted);
+ full_dump_whitelist_ = whitelist;
+ }
+ void enable_print_smaps() { print_smaps_ = true; }
+
+ private:
+ AtraceProcessDump(const AtraceProcessDump&) = delete;
+ void operator=(const AtraceProcessDump&) = delete;
+
+ using ProcessMap = std::map<int, std::unique_ptr<ProcessInfo>>;
+ using ProcessSnapshotMap = std::map<int, std::unique_ptr<ProcessSnapshot>>;
+
+ void TakeGlobalSnapshot();
+ void TakeAndSerializeMemInfo();
+ bool UpdatePersistentProcessInfo(int pid);
+ bool ShouldTakeFullDump(const ProcessInfo* process);
+ void SerializeSnapshot();
+ void SerializePersistentProcessInfo();
+ void Cleanup();
+
+ int self_pid_;
+ int dump_count_;
+ bool graphics_stats_ = false;
+ bool print_smaps_ = false;
+ FullDumpMode full_dump_mode_ = FullDumpMode::kDisabled;
+ std::set<std::string> full_dump_whitelist_;
+
+ FILE* out_;
+ ProcessMap processes_;
+ ProcessSnapshotMap snapshot_;
+ uint64_t snapshot_timestamp_;
+ std::set<int> full_dump_whitelisted_pids_;
+ std::unique_ptr<time_utils::PeriodicTimer> snapshot_timer_;
+ int dump_interval_in_timer_ticks_;
+};
+
+#endif // ATRACE_PROCESS_DUMP_H_
diff --git a/src/atrace_helper/file_utils.cc b/src/atrace_helper/file_utils.cc
new file mode 100644
index 0000000..7d122d6
--- /dev/null
+++ b/src/atrace_helper/file_utils.cc
@@ -0,0 +1,117 @@
+// Copyright 2017 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 "file_utils.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+namespace {
+
+bool IsNumeric(const char* str) {
+ if (!str[0])
+ return false;
+ for (const char* c = str; *c; c++) {
+ if (!isdigit(*c))
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace file_utils {
+
+void ForEachPidInProcPath(const char* proc_path,
+ std::function<void(int)> predicate) {
+ DIR* root_dir = opendir(proc_path);
+ ScopedDir autoclose(root_dir);
+ struct dirent* child_dir;
+ while ((child_dir = readdir(root_dir))) {
+ if (child_dir->d_type != DT_DIR || !IsNumeric(child_dir->d_name))
+ continue;
+ predicate(atoi(child_dir->d_name));
+ }
+}
+
+ssize_t ReadFile(const char* path, char* buf, size_t length) {
+ buf[0] = '\0';
+ int fd = open(path, O_RDONLY);
+ if (fd < 0 && errno == ENOENT)
+ return -1;
+ ScopedFD autoclose(fd);
+ size_t tot_read = 0;
+ do {
+ ssize_t rsize = read(fd, buf + tot_read, length - tot_read);
+ if (rsize == 0)
+ break;
+ if (rsize == -1 && errno == EINTR)
+ continue;
+ else if (rsize < 0)
+ return -1;
+ tot_read += static_cast<size_t>(rsize);
+ } while (tot_read < length);
+ buf[tot_read < length ? tot_read : length - 1] = '\0';
+ return tot_read;
+}
+
+bool ReadFileTrimmed(const char* path, char* buf, size_t length) {
+ ssize_t rsize = ReadFile(path, buf, length);
+ if (rsize < 0)
+ return false;
+ for (ssize_t i = 0; i < rsize; i++) {
+ const char c = buf[i];
+ if (c == '\0' || c == '\r' || c == '\n') {
+ buf[i] = '\0';
+ break;
+ }
+ buf[i] = isprint(c) ? c : '?';
+ }
+ return true;
+}
+
+ssize_t ReadProcFile(int pid, const char* proc_file, char* buf, size_t length) {
+ char proc_path[128];
+ snprintf(proc_path, sizeof(proc_path), "/proc/%d/%s", pid, proc_file);
+ return ReadFile(proc_path, buf, length);
+}
+
+// Reads a single-line proc file, stripping out any \0, \r, \n and replacing
+// non-printable charcters with '?'.
+bool ReadProcFileTrimmed(int pid,
+ const char* proc_file,
+ char* buf,
+ size_t length) {
+ char proc_path[128];
+ snprintf(proc_path, sizeof(proc_path), "/proc/%d/%s", pid, proc_file);
+ return ReadFileTrimmed(proc_path, buf, length);
+}
+
+LineReader::LineReader(char* buf, size_t size)
+ : ptr_(buf), end_(buf + size) {
+}
+
+LineReader::~LineReader() {
+}
+
+const char* LineReader::NextLine() {
+ if (ptr_ >= end_)
+ return nullptr;
+ const char* cur = ptr_;
+ char* next = strchr(ptr_, '\n');
+ if (next) {
+ *next = '\0';
+ ptr_ = next + 1;
+ } else {
+ ptr_ = end_;
+ }
+ return cur;
+}
+
+} // namespace file_utils
diff --git a/src/atrace_helper/file_utils.h b/src/atrace_helper/file_utils.h
new file mode 100644
index 0000000..2b2556b
--- /dev/null
+++ b/src/atrace_helper/file_utils.h
@@ -0,0 +1,67 @@
+// Copyright 2017 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 FILE_UTILS_H_
+#define FILE_UTILS_H_
+
+#include <dirent.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <functional>
+#include <map>
+#include <memory>
+
+#include "logging.h"
+
+namespace file_utils {
+
+// RAII classes for auto-releasing fd/dirs.
+template <typename RESOURCE_TYPE, int (*CLOSE_FN)(RESOURCE_TYPE)>
+struct ScopedResource {
+ explicit ScopedResource(RESOURCE_TYPE r) : r_(r) { CHECK(r); }
+ ~ScopedResource() { CLOSE_FN(r_); }
+ RESOURCE_TYPE r_;
+};
+
+using ScopedFD = ScopedResource<int, close>;
+using ScopedDir = ScopedResource<DIR*, closedir>;
+
+// Invokes predicate(pid) for each folder in |proc_path|/[0-9]+ which has
+// a numeric name (typically pids and tids).
+void ForEachPidInProcPath(const char* proc_path,
+ std::function<void(int)> predicate);
+
+// Reads the contents of |path| fully into |buf| up to |length| chars.
+// |buf| is guaranteed to be null terminated.
+ssize_t ReadFile(const char* path, char* buf, size_t length);
+
+// Reads a single-line file, stripping out any \0, \r, \n and replacing
+// non-printable charcters with '?'. |buf| is guaranteed to be null terminated.
+bool ReadFileTrimmed(const char* path, char* buf, size_t length);
+
+// Convenience wrappers for /proc/|pid|/|proc_file| paths.
+ssize_t ReadProcFile(int pid, const char* proc_file, char* buf, size_t length);
+bool ReadProcFileTrimmed(int pid,
+ const char* proc_file,
+ char* buf,
+ size_t length);
+
+// Takes a C string buffer and chunks it into lines without creating any
+// copies. It modifies the original buffer, by replacing \n with \0.
+class LineReader {
+ public:
+ LineReader(char* buf, size_t size);
+ ~LineReader();
+
+ const char* NextLine();
+
+ private:
+ char* ptr_;
+ char* end_;
+};
+
+} // namespace file_utils
+
+#endif // FILE_UTILS_H_
diff --git a/src/atrace_helper/logging.h b/src/atrace_helper/logging.h
new file mode 100644
index 0000000..ecbb56b
--- /dev/null
+++ b/src/atrace_helper/logging.h
@@ -0,0 +1,32 @@
+// Copyright 2017 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 LOGGING_H_
+#define LOGGING_H_
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CHECK_ARGS(COND, ERR) \
+ "FAILED CHECK(%s) @ %s:%d (errno: %s)\n", #COND, __FILE__, __LINE__, \
+ strerror(ERR)
+
+#define CHECK(x) \
+ do { \
+ if (!(x)) { \
+ const int e = errno; \
+ fprintf(stderr, "\n" CHECK_ARGS(x, e)); \
+ fflush(stderr); \
+ abort(); \
+ } \
+ } while (0)
+
+inline void LogError(const char* message) {
+ fprintf(stderr, "\n%s\n", message);
+ fflush(stderr);
+}
+
+#endif // LOGGING_H_
diff --git a/src/atrace_helper/main.cc b/src/atrace_helper/main.cc
new file mode 100644
index 0000000..35c36b0
--- /dev/null
+++ b/src/atrace_helper/main.cc
@@ -0,0 +1,134 @@
+// Copyright 2017 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 <inttypes.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <sstream>
+
+#include "atrace_process_dump.h"
+#include "logging.h"
+
+#define PATH_MAX 256
+
+namespace {
+
+std::unique_ptr<AtraceProcessDump> g_prog;
+
+void ParseFullDumpConfig(const std::string& config, AtraceProcessDump* prog) {
+ using FullDumpMode = AtraceProcessDump::FullDumpMode;
+ if (config == "all") {
+ prog->set_full_dump_mode(FullDumpMode::kAllProcesses);
+ } else if (config == "apps") {
+ prog->set_full_dump_mode(FullDumpMode::kAllJavaApps);
+ } else {
+ std::set<std::string> whitelist;
+ std::istringstream ss(config);
+ std::string entry;
+ while (std::getline(ss, entry, ',')) {
+ whitelist.insert(entry);
+ }
+ if (whitelist.empty())
+ return;
+ prog->set_full_dump_mode(FullDumpMode::kOnlyWhitelisted);
+ prog->set_full_dump_whitelist(whitelist);
+ }
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ if (argc == 2 && !strcmp(argv[1], "--echo-ts")) {
+ // Used by clock sync marker to correct the difference between
+ // Linux monotonic clocks on the device and host.
+ printf("%" PRIu64 "\n", time_utils::GetTimestamp());
+ return 0;
+ }
+
+ bool background = false;
+ int dump_interval_ms = 5000;
+ char out_file[PATH_MAX] = {};
+ bool dump_to_file = false;
+ int count = -1;
+
+ AtraceProcessDump* prog = new AtraceProcessDump();
+ g_prog = std::unique_ptr<AtraceProcessDump>(prog);
+
+ if (geteuid()) {
+ fprintf(stderr, "Must run as root\n");
+ exit(EXIT_FAILURE);
+ }
+
+ int opt;
+ while ((opt = getopt(argc, argv, "bm:st:o:c:")) != -1) {
+ switch (opt) {
+ case 'b':
+ background = true;
+ break;
+ case 'm':
+ ParseFullDumpConfig(optarg, prog);
+ break;
+ case 's':
+ prog->enable_print_smaps();
+ break;
+ case 't':
+ dump_interval_ms = atoi(optarg);
+ CHECK(dump_interval_ms > 0);
+ break;
+ case 'c':
+ count = atoi(optarg);
+ CHECK(count > 0);
+ break;
+ case 'o':
+ strncpy(out_file, optarg, sizeof(out_file));
+ out_file[PATH_MAX - 1] = '\0';
+ dump_to_file = true;
+ break;
+ default:
+ fprintf(stderr,
+ "Usage: %s [-b] [-m full_dump_filter] [-s] "
+ "[-t dump_interval_ms] "
+ "[-c dumps_count] [-o out.json]\n",
+ argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ prog->set_dump_count(count);
+ prog->SetDumpInterval(dump_interval_ms);
+
+ FILE* out_stream = stdout;
+ char tmp_file[PATH_MAX + 4];
+ if (dump_to_file) {
+ unlink(out_file);
+ sprintf(tmp_file, "%s.tmp", out_file);
+ out_stream = fopen(tmp_file, "w");
+ CHECK(out_stream);
+ }
+
+ if (background) {
+ if (!dump_to_file) {
+ fprintf(stderr, "-b requires -o for output dump path.\n");
+ exit(EXIT_FAILURE);
+ }
+ printf("Continuing in background. kill -TERM to terminate the daemon.\n");
+ CHECK(daemon(0 /* nochdir */, 0 /* noclose */) == 0);
+ }
+
+ auto on_exit = [](int) { g_prog->Stop(); };
+ signal(SIGINT, on_exit);
+ signal(SIGTERM, on_exit);
+
+ prog->RunAndPrintJson(out_stream);
+ fclose(out_stream);
+
+ if (dump_to_file)
+ rename(tmp_file, out_file);
+}
diff --git a/src/atrace_helper/process_info.h b/src/atrace_helper/process_info.h
new file mode 100644
index 0000000..089e77e
--- /dev/null
+++ b/src/atrace_helper/process_info.h
@@ -0,0 +1,40 @@
+// Copyright 2017 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 PROCESS_INFO_H_
+#define PROCESS_INFO_H_
+
+#include <map>
+
+#include "process_memory_stats.h"
+
+struct ThreadInfo {
+ int tid;
+ char name[16];
+};
+
+struct ProcessInfo {
+ int pid;
+ bool in_kernel;
+ bool is_app;
+ char name[256];
+ char exe[256];
+ std::map<int, ThreadInfo> threads;
+};
+
+struct ProcessSnapshot {
+ int pid;
+ ProcessMemoryStats memory;
+ // OOM badness and tolerance (oom_adj is deprecated).
+ int oom_score;
+ int oom_score_adj;
+ // Page faults.
+ unsigned long minor_faults;
+ unsigned long major_faults;
+ // Time spent in userspace and in the kernel.
+ unsigned long utime;
+ unsigned long stime;
+};
+
+#endif // PROCESS_INFO_H_
diff --git a/src/atrace_helper/process_memory_stats.cc b/src/atrace_helper/process_memory_stats.cc
new file mode 100644
index 0000000..141c841
--- /dev/null
+++ b/src/atrace_helper/process_memory_stats.cc
@@ -0,0 +1,124 @@
+// Copyright 2017 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 "process_memory_stats.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <memory>
+
+#include "file_utils.h"
+#include "logging.h"
+
+namespace {
+
+const int kKbPerPage = 4;
+
+const char kRss[] = "Rss";
+const char kPss[] = "Pss";
+const char kSwap[] = "Swap";
+const char kSharedClean[] = "Shared_Clean";
+const char kSharedDirty[] = "Shared_Dirty";
+const char kPrivateClean[] = "Private_Clean";
+const char kPrivateDirty[] = "Private_Dirty";
+
+bool ReadSmapsMetric(
+ const char* line, const char* metric, int metric_size, uint64_t* res) {
+ if (strncmp(line, metric, metric_size - 1))
+ return false;
+ if (line[metric_size - 1] != ':')
+ return false;
+ *res = strtoull(line + metric_size, nullptr, 10);
+ return true;
+}
+
+} // namespace
+
+bool ProcessMemoryStats::ReadLightStats(int pid) {
+ char buf[64];
+ if (file_utils::ReadProcFile(pid, "statm", buf, sizeof(buf)) <= 0)
+ return false;
+ uint32_t vm_size_pages;
+ uint32_t rss_pages;
+ int res = sscanf(buf, "%u %u", &vm_size_pages, &rss_pages);
+ CHECK(res == 2);
+ rss_kb_ = rss_pages * kKbPerPage;
+ virt_kb_ = vm_size_pages * kKbPerPage;
+ return true;
+}
+
+bool ProcessMemoryStats::ReadFullStats(int pid) {
+ const size_t kBufSize = 8u * 1024 * 1024;
+ std::unique_ptr<char[]> buf(new char[kBufSize]);
+ ssize_t rsize = file_utils::ReadProcFile(pid, "smaps", &buf[0], kBufSize);
+ if (rsize <= 0)
+ return false;
+ MmapInfo* last_mmap_entry = nullptr;
+ std::unique_ptr<MmapInfo> new_mmap(new MmapInfo());
+ CHECK(mmaps_.empty());
+ CHECK(rss_kb_ == 0);
+
+ // Iterate over all lines in /proc/PID/smaps.
+ file_utils::LineReader rd(&buf[0], rsize);
+ for (const char* line = rd.NextLine(); line; line = rd.NextLine()) {
+ if (!line[0])
+ continue;
+ // Performance optimization (hack).
+ // Any header line starts with lowercase hex digit but subsequent lines
+ // start with uppercase letter.
+ if (line[0] < 'A' || line[0] > 'Z') {
+ // Note that the mapped file name ([stack]) is optional and won't be
+ // present on anonymous memory maps (hence res >= 3 below).
+ int res = sscanf(line,
+ "%llx-%llx %4s %*x %*[:0-9a-f] "
+ "%*[0-9a-f]%*[ \t]%127[^\n]",
+ &new_mmap->start_addr, &new_mmap->end_addr, new_mmap->prot_flags,
+ new_mmap->mapped_file);
+ last_mmap_entry = new_mmap.get();
+ CHECK(new_mmap->end_addr >= new_mmap->start_addr);
+ new_mmap->virt_kb =
+ (new_mmap->end_addr - new_mmap->start_addr) / 1024;
+ if (res == 3)
+ new_mmap->mapped_file[0] = '\0';
+ virt_kb_ += new_mmap->virt_kb;
+ mmaps_.push_back(std::move(new_mmap));
+ new_mmap.reset(new MmapInfo());
+ } else {
+ // The current line is a metrics line within a mmap entry, e.g.:
+ // Size: 4 kB
+ uint64_t size = 0;
+ CHECK(last_mmap_entry);
+ if (ReadSmapsMetric(line, kRss, sizeof(kRss), &size)) {
+ last_mmap_entry->rss_kb = size;
+ rss_kb_ += size;
+ } else if (ReadSmapsMetric(line, kPss, sizeof(kPss), &size)) {
+ last_mmap_entry->pss_kb = size;
+ pss_kb_ += size;
+ } else if (ReadSmapsMetric(line, kSwap, sizeof(kSwap), &size)) {
+ last_mmap_entry->swapped_kb = size;
+ swapped_kb_ += size;
+ } else if (ReadSmapsMetric(
+ line, kSharedClean, sizeof(kSharedClean), &size)) {
+ last_mmap_entry->shared_clean_kb = size;
+ shared_clean_kb_ += size;
+ } else if (ReadSmapsMetric(
+ line, kSharedDirty, sizeof(kSharedDirty), &size)) {
+ last_mmap_entry->shared_dirty_kb = size;
+ shared_dirty_kb_ += size;
+ } else if (ReadSmapsMetric(
+ line, kPrivateClean, sizeof(kPrivateClean), &size)) {
+ last_mmap_entry->private_clean_kb = size;
+ private_clean_kb_ += size;
+ } else if (ReadSmapsMetric(
+ line, kPrivateDirty, sizeof(kPrivateDirty), &size)) {
+ last_mmap_entry->private_dirty_kb = size;
+ private_dirty_kb_ += size;
+ }
+ }
+ }
+ full_stats_ = true;
+ return true;
+}
diff --git a/src/atrace_helper/process_memory_stats.h b/src/atrace_helper/process_memory_stats.h
new file mode 100644
index 0000000..cb72c9c
--- /dev/null
+++ b/src/atrace_helper/process_memory_stats.h
@@ -0,0 +1,72 @@
+// Copyright 2017 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 PROCESS_MEMORY_STATS_H_
+#define PROCESS_MEMORY_STATS_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+// Reads process memory stats from /proc/pid/{statm,smaps}.
+class ProcessMemoryStats {
+ public:
+ struct MmapInfo {
+ char mapped_file[128] = {};
+ char prot_flags[5] = {};
+ uint64_t start_addr = 0;
+ uint64_t end_addr = 0;
+ uint64_t virt_kb = 0;
+ uint64_t pss_kb = 0; // Proportional Set Size.
+ uint64_t rss_kb = 0; // Resident Set Size.
+ uint64_t private_clean_kb = 0;
+ uint64_t private_dirty_kb = 0;
+ uint64_t shared_clean_kb = 0;
+ uint64_t shared_dirty_kb = 0;
+ uint64_t swapped_kb = 0;
+ };
+
+ ProcessMemoryStats() {}
+
+ bool ReadLightStats(int pid);
+ bool ReadFullStats(int pid);
+
+ // Available after ReadLightStats().
+ uint64_t virt_kb() const { return virt_kb_; }
+ uint64_t rss_kb() const { return rss_kb_; }
+
+ // Available after ReadFullStats().
+ bool full_stats_available() const { return full_stats_; }
+ uint64_t pss_kb() const { return pss_kb_; }
+ uint64_t private_clean_kb() const { return private_clean_kb_; }
+ uint64_t private_dirty_kb() const { return private_dirty_kb_; }
+ uint64_t shared_clean_kb() const { return shared_clean_kb_; }
+ uint64_t shared_dirty_kb() const { return shared_dirty_kb_; }
+ uint64_t swapped_kb() const { return swapped_kb_; }
+
+ size_t mmaps_count() const { return mmaps_.size(); }
+ const MmapInfo* mmap(size_t index) const { return mmaps_[index].get(); }
+
+ private:
+ ProcessMemoryStats(const ProcessMemoryStats&) = delete;
+ void operator=(const ProcessMemoryStats&) = delete;
+
+ // Light stats.
+ uint64_t virt_kb_ = 0;
+ uint64_t rss_kb_ = 0;
+
+ // Full stats.
+ bool full_stats_ = false;
+ uint64_t pss_kb_ = 0;
+ uint64_t private_clean_kb_ = 0;
+ uint64_t private_dirty_kb_ = 0;
+ uint64_t shared_clean_kb_ = 0;
+ uint64_t shared_dirty_kb_ = 0;
+ uint64_t swapped_kb_ = 0;
+
+ std::vector<std::unique_ptr<const MmapInfo>> mmaps_;
+};
+
+#endif // PROCESS_MEMORY_STATS_H_
diff --git a/src/atrace_helper/procfs_utils.cc b/src/atrace_helper/procfs_utils.cc
new file mode 100644
index 0000000..bf1ee4a
--- /dev/null
+++ b/src/atrace_helper/procfs_utils.cc
@@ -0,0 +1,137 @@
+// Copyright 2017 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 "procfs_utils.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "file_utils.h"
+#include "logging.h"
+
+using file_utils::ForEachPidInProcPath;
+using file_utils::ReadProcFile;
+using file_utils::ReadProcFileTrimmed;
+
+namespace procfs_utils {
+
+namespace {
+
+const char kJavaAppPrefix[] = "/system/bin/app_process";
+const char kZygotePrefix[] = "zygote";
+
+inline void ReadProcString(int pid, const char* path, char* buf, size_t size) {
+ if (!file_utils::ReadProcFileTrimmed(pid, path, buf, size))
+ buf[0] = '\0';
+}
+
+inline void ReadExePath(int pid, char* buf, size_t size) {
+ char exe_path[64];
+ sprintf(exe_path, "/proc/%d/exe", pid);
+ ssize_t res = readlink(exe_path, buf, size - 1);
+ if (res >= 0)
+ buf[res] = '\0';
+ else
+ buf[0] = '\0';
+}
+
+inline bool IsApp(const char* name, const char* exe) {
+ return strncmp(exe, kJavaAppPrefix, sizeof(kJavaAppPrefix) - 1) == 0 &&
+ strncmp(name, kZygotePrefix, sizeof(kZygotePrefix) - 1) != 0;
+}
+
+} // namespace
+
+int ReadTgid(int pid) {
+ static const char kTgid[] = "\nTgid:";
+ char buf[512];
+ ssize_t rsize = ReadProcFile(pid, "status", buf, sizeof(buf));
+ if (rsize <= 0)
+ return -1;
+ const char* tgid_line = strstr(buf, kTgid);
+ CHECK(tgid_line);
+ return atoi(tgid_line + sizeof(kTgid) - 1);
+}
+
+std::unique_ptr<ProcessInfo> ReadProcessInfo(int pid) {
+ ProcessInfo* process = new ProcessInfo();
+ process->pid = pid;
+ ReadProcString(pid, "cmdline", process->name, sizeof(process->name));
+ if (process->name[0] != 0) {
+ ReadExePath(pid, process->exe, sizeof(process->exe));
+ process->is_app = IsApp(process->name, process->exe);
+ } else {
+ ReadProcString(pid, "comm", process->name, sizeof(process->name));
+ CHECK(process->name[0]);
+ process->in_kernel = true;
+ }
+ return std::unique_ptr<ProcessInfo>(process);
+}
+
+void ReadProcessThreads(ProcessInfo* process) {
+ if (process->in_kernel)
+ return;
+
+ char tasks_path[64];
+ sprintf(tasks_path, "/proc/%d/task", process->pid);
+ ForEachPidInProcPath(tasks_path, [process](int tid) {
+ if (process->threads.count(tid))
+ return;
+ ThreadInfo thread = { tid, "" };
+ char task_comm[64];
+ sprintf(task_comm, "task/%d/comm", tid);
+ ReadProcString(process->pid, task_comm, thread.name, sizeof(thread.name));
+ if (thread.name[0] == '\0' && process->is_app)
+ strcpy(thread.name, "UI Thread");
+ process->threads[tid] = thread;
+ });
+}
+
+bool ReadOomStats(ProcessSnapshot* snapshot) {
+ char buf[64];
+ if (ReadProcFileTrimmed(snapshot->pid, "oom_score", buf, sizeof(buf)))
+ snapshot->oom_score = atoi(buf);
+ else
+ return false;
+ if (ReadProcFileTrimmed(snapshot->pid, "oom_score_adj", buf, sizeof(buf)))
+ snapshot->oom_score_adj = atoi(buf);
+ else
+ return false;
+ return true;
+}
+
+bool ReadPageFaultsAndCpuTimeStats(ProcessSnapshot* snapshot) {
+ char buf[512];
+ if (!ReadProcFileTrimmed(snapshot->pid, "stat", buf, sizeof(buf)))
+ return false;
+ int ret = sscanf(buf,
+ "%*d %*s %*c %*d %*d %*d %*d %*d %*u %lu %*u %lu %*u %lu %lu",
+ &snapshot->minor_faults, &snapshot->major_faults,
+ &snapshot->utime, &snapshot->stime);
+ printf("ret is %d[%s]\n", ret, buf);
+ CHECK(ret == 4);
+ return true;
+}
+
+bool ReadMemInfoStats(std::map<std::string, uint64_t>* mem_info) {
+ char buf[1024];
+ ssize_t rsize = file_utils::ReadFile("/proc/meminfo", buf, sizeof(buf));
+ if (rsize <= 0)
+ return false;
+
+ file_utils::LineReader reader(buf, rsize);
+ for (const char* line = reader.NextLine();
+ line && line[0];
+ line = reader.NextLine()) {
+
+ const char* pos_colon = strstr(line, ":");
+ if (pos_colon == nullptr)
+ continue; // Should not happen.
+ std::string name(line, pos_colon - line);
+ (*mem_info)[name] = strtoull(&pos_colon[1], nullptr, 10);
+ }
+ return true;
+}
+
+} // namespace procfs_utils
diff --git a/src/atrace_helper/procfs_utils.h b/src/atrace_helper/procfs_utils.h
new file mode 100644
index 0000000..e5ce704
--- /dev/null
+++ b/src/atrace_helper/procfs_utils.h
@@ -0,0 +1,30 @@
+// Copyright 2017 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 PROCFS_UTILS_H_
+#define PROCFS_UTILS_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "process_info.h"
+
+namespace procfs_utils {
+
+// ProcFS doesn't necessarly distinguish PID vs. TID, but all threads of a
+// process have the same Thread Group ID which is equal to Process ID.
+int ReadTgid(int pid);
+
+std::unique_ptr<ProcessInfo> ReadProcessInfo(int pid);
+void ReadProcessThreads(ProcessInfo* process);
+
+bool ReadOomStats(ProcessSnapshot* snapshot);
+bool ReadPageFaultsAndCpuTimeStats(ProcessSnapshot* snapshot);
+
+bool ReadMemInfoStats(std::map<std::string, uint64_t>* mem_info);
+
+} // namespace procfs_utils
+
+#endif // PROCFS_UTILS_H_
diff --git a/src/atrace_helper/time_utils.cc b/src/atrace_helper/time_utils.cc
new file mode 100644
index 0000000..3c8aa16
--- /dev/null
+++ b/src/atrace_helper/time_utils.cc
@@ -0,0 +1,62 @@
+// Copyright 2017 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 "time_utils.h"
+
+#include <sys/time.h>
+#include <sys/timerfd.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "logging.h"
+
+namespace time_utils {
+
+uint64_t GetTimestamp() {
+ struct timespec ts = {};
+ CHECK(clock_gettime(CLOCK_MONOTONIC_COARSE, &ts) == 0);
+ return ts.tv_sec * 1000 + ts.tv_nsec / 1000000ul;
+}
+
+PeriodicTimer::PeriodicTimer(int interval_ms) : interval_ms_(interval_ms) {
+ timer_fd_ = -1;
+}
+
+PeriodicTimer::~PeriodicTimer() {
+ Stop();
+}
+
+void PeriodicTimer::Start() {
+ Stop();
+ timer_fd_ = timerfd_create(CLOCK_MONOTONIC, 0);
+ CHECK(timer_fd_ >= 0);
+ int sec = interval_ms_ / 1000;
+ int nsec = (interval_ms_ % 1000) * 1000000;
+ struct itimerspec ts = {};
+ ts.it_value.tv_nsec = nsec;
+ ts.it_value.tv_sec = sec;
+ ts.it_interval.tv_nsec = nsec;
+ ts.it_interval.tv_sec = sec;
+ CHECK(timerfd_settime(timer_fd_, 0, &ts, nullptr) == 0);
+}
+
+void PeriodicTimer::Stop() {
+ if (timer_fd_ < 0)
+ return;
+ close(timer_fd_);
+ timer_fd_ = -1;
+}
+
+bool PeriodicTimer::Wait() {
+ if (timer_fd_ < 0)
+ return false; // Not started yet.
+ uint64_t stub = 0;
+ int res = read(timer_fd_, &stub, sizeof(stub));
+ if (res < 0 && errno == EBADF)
+ return false; // Interrupted by Stop().
+ CHECK(res > 0);
+ return true;
+}
+
+} // namespace time_utils
diff --git a/src/atrace_helper/time_utils.h b/src/atrace_helper/time_utils.h
new file mode 100644
index 0000000..2319601
--- /dev/null
+++ b/src/atrace_helper/time_utils.h
@@ -0,0 +1,34 @@
+// Copyright 2017 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 TIME_UTILS_H_
+#define TIME_UTILS_H_
+
+#include <stdint.h>
+
+namespace time_utils {
+
+uint64_t GetTimestamp();
+
+class PeriodicTimer {
+ public:
+ PeriodicTimer(int interval_ms);
+ ~PeriodicTimer();
+
+ void Start();
+ void Stop();
+ // Wait for next tick. Returns false if interrupted by Stop() or not started.
+ bool Wait();
+
+ private:
+ PeriodicTimer(const PeriodicTimer&) = delete;
+ void operator=(const PeriodicTimer&) = delete;
+
+ const int interval_ms_;
+ int timer_fd_;
+};
+
+} // namespace time_utils
+
+#endif // TIME_UTILS_