diff options
author | Seungha Son <seungha.son@samsung.com> | 2020-04-01 10:35:13 +0900 |
---|---|---|
committer | Seungha Son <seungha.son@samsung.com> | 2020-04-08 15:05:54 +0900 |
commit | ac6ef8f388ce2062621ac639e77a9a3b04ae3402 (patch) | |
tree | 4728ddcd9fbf6ed80530b899785f75060ca4e321 | |
parent | 016367713f8719af4c1c4578c8aa7d6215f678e3 (diff) | |
download | ttrace-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-x | CMakeLists.txt | 24 | ||||
-rw-r--r-- | packaging/ttrace.spec | 1 | ||||
-rw-r--r-- | src/atrace_helper/atrace_process_dump.cc | 244 | ||||
-rw-r--r-- | src/atrace_helper/atrace_process_dump.h | 81 | ||||
-rw-r--r-- | src/atrace_helper/file_utils.cc | 117 | ||||
-rw-r--r-- | src/atrace_helper/file_utils.h | 67 | ||||
-rw-r--r-- | src/atrace_helper/logging.h | 32 | ||||
-rw-r--r-- | src/atrace_helper/main.cc | 134 | ||||
-rw-r--r-- | src/atrace_helper/process_info.h | 40 | ||||
-rw-r--r-- | src/atrace_helper/process_memory_stats.cc | 124 | ||||
-rw-r--r-- | src/atrace_helper/process_memory_stats.h | 72 | ||||
-rw-r--r-- | src/atrace_helper/procfs_utils.cc | 137 | ||||
-rw-r--r-- | src/atrace_helper/procfs_utils.h | 30 | ||||
-rw-r--r-- | src/atrace_helper/time_utils.cc | 62 | ||||
-rw-r--r-- | src/atrace_helper/time_utils.h | 34 |
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_ |