// 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); } if (graphics_stats_ && process->is_app) { process_snapshot->memory.ReadGpuStats(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()); } if (mem->gpu_stats_available()) { fprintf(out_, ",\"gpu_egl\":%" PRIu64 ",\"gpu_egl_pss\":%" PRIu64 ",\"gpu_gl\":%" PRIu64 ",\"gpu_gl_pss\":%" PRIu64 ",\"gpu_etc\":%" PRIu64 ",\"gpu_etc_pss\":%" PRIu64, mem->gpu_graphics_kb(), mem->gpu_graphics_pss_kb(), mem->gpu_gl_kb(), mem->gpu_gl_pss_kb(), mem->gpu_other_kb(), mem->gpu_other_pss_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; }