Add atrace-helper module 96/229396/4
authorSeungha Son <seungha.son@samsung.com>
Wed, 1 Apr 2020 01:35:13 +0000 (10:35 +0900)
committerSeungha Son <seungha.son@samsung.com>
Wed, 8 Apr 2020 06:05:54 +0000 (15:05 +0900)
 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>
15 files changed:
CMakeLists.txt
packaging/ttrace.spec
src/atrace_helper/atrace_process_dump.cc [new file with mode: 0644]
src/atrace_helper/atrace_process_dump.h [new file with mode: 0644]
src/atrace_helper/file_utils.cc [new file with mode: 0644]
src/atrace_helper/file_utils.h [new file with mode: 0644]
src/atrace_helper/logging.h [new file with mode: 0644]
src/atrace_helper/main.cc [new file with mode: 0644]
src/atrace_helper/process_info.h [new file with mode: 0644]
src/atrace_helper/process_memory_stats.cc [new file with mode: 0644]
src/atrace_helper/process_memory_stats.h [new file with mode: 0644]
src/atrace_helper/procfs_utils.cc [new file with mode: 0644]
src/atrace_helper/procfs_utils.h [new file with mode: 0644]
src/atrace_helper/time_utils.cc [new file with mode: 0644]
src/atrace_helper/time_utils.h [new file with mode: 0644]

index ca1c48ff8196099964004306a5a93b6b516585ac..1e82b2c7ed87c82abd6b63ef0894aa18eb446f9e 100755 (executable)
@@ -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)
index 5ee3a9e287e17d11d8b59ddaaebc73b9fc94fac9..d92e950f02db8d8945a2920179fedea5537a48f5 100644 (file)
@@ -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 (file)
index 0000000..b6e8a29
--- /dev/null
@@ -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 (file)
index 0000000..280a18a
--- /dev/null
@@ -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 (file)
index 0000000..7d122d6
--- /dev/null
@@ -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 (file)
index 0000000..2b2556b
--- /dev/null
@@ -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 (file)
index 0000000..ecbb56b
--- /dev/null
@@ -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 (file)
index 0000000..35c36b0
--- /dev/null
@@ -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 (file)
index 0000000..089e77e
--- /dev/null
@@ -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 (file)
index 0000000..141c841
--- /dev/null
@@ -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 (file)
index 0000000..cb72c9c
--- /dev/null
@@ -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 (file)
index 0000000..bf1ee4a
--- /dev/null
@@ -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 (file)
index 0000000..e5ce704
--- /dev/null
@@ -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 (file)
index 0000000..3c8aa16
--- /dev/null
@@ -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 (file)
index 0000000..2319601
--- /dev/null
@@ -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_