blob: 8100e426425c6c9d3aadfd4edd21880bbc7d2bbc [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/traced/probes/ps/process_stats_data_source.h"
#include <stdlib.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <utility>
#include "perfetto/base/task_runner.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/metatrace.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/tracing/core/data_source_config.h"
#include "protos/perfetto/config/process_stats/process_stats_config.pbzero.h"
#include "protos/perfetto/trace/ps/process_stats.pbzero.h"
#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
// TODO(primiano): the code in this file assumes that PIDs are never recycled
// and that processes/threads never change names. Neither is always true.
// The notion of PID in the Linux kernel is a bit confusing.
// - PID: is really the thread id (for the main thread: PID == TID).
// - TGID (thread group ID): is the Unix Process ID (the actual PID).
// - PID == TGID for the main thread: the TID of the main thread is also the PID
// of the process.
// So, in this file, |pid| might refer to either a process id or a thread id.
namespace perfetto {
namespace {
int32_t ReadNextNumericDir(DIR* dirp) {
while (struct dirent* dir_ent = readdir(dirp)) {
if (dir_ent->d_type != DT_DIR)
continue;
auto int_value = base::CStringToInt32(dir_ent->d_name);
if (int_value)
return *int_value;
}
return 0;
}
inline int ToInt(const std::string& str) {
return atoi(str.c_str());
}
inline uint32_t ToU32(const char* str) {
return static_cast<uint32_t>(strtol(str, nullptr, 10));
}
} // namespace
// static
const ProbesDataSource::Descriptor ProcessStatsDataSource::descriptor = {
/*name*/ "linux.process_stats",
/*flags*/ Descriptor::kHandlesIncrementalState,
/*fill_descriptor_func*/ nullptr,
};
ProcessStatsDataSource::ProcessStatsDataSource(
base::TaskRunner* task_runner,
TracingSessionID session_id,
std::unique_ptr<TraceWriter> writer,
const DataSourceConfig& ds_config,
std::unique_ptr<CpuFreqInfo> cpu_freq_info)
: ProbesDataSource(session_id, &descriptor),
task_runner_(task_runner),
writer_(std::move(writer)),
cpu_freq_info_(std::move(cpu_freq_info)),
weak_factory_(this) {
using protos::pbzero::ProcessStatsConfig;
ProcessStatsConfig::Decoder cfg(ds_config.process_stats_config_raw());
record_thread_names_ = cfg.record_thread_names();
dump_all_procs_on_start_ = cfg.scan_all_processes_on_start();
resolve_process_fds_ = cfg.resolve_process_fds();
enable_on_demand_dumps_ = true;
for (auto quirk = cfg.quirks(); quirk; ++quirk) {
if (*quirk == ProcessStatsConfig::DISABLE_ON_DEMAND)
enable_on_demand_dumps_ = false;
}
poll_period_ms_ = cfg.proc_stats_poll_ms();
if (poll_period_ms_ > 0 && poll_period_ms_ < 100) {
PERFETTO_ILOG("proc_stats_poll_ms %" PRIu32
" is less than minimum of 100ms. Increasing to 100ms.",
poll_period_ms_);
poll_period_ms_ = 100;
}
if (poll_period_ms_ > 0) {
auto proc_stats_ttl_ms = cfg.proc_stats_cache_ttl_ms();
process_stats_cache_ttl_ticks_ =
std::max(proc_stats_ttl_ms / poll_period_ms_, 1u);
}
}
ProcessStatsDataSource::~ProcessStatsDataSource() = default;
void ProcessStatsDataSource::Start() {
if (dump_all_procs_on_start_) {
WriteAllProcesses();
}
if (poll_period_ms_) {
auto weak_this = GetWeakPtr();
task_runner_->PostTask(std::bind(&ProcessStatsDataSource::Tick, weak_this));
}
}
base::WeakPtr<ProcessStatsDataSource> ProcessStatsDataSource::GetWeakPtr()
const {
return weak_factory_.GetWeakPtr();
}
void ProcessStatsDataSource::WriteAllProcesses() {
PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_WRITE_ALL_PROCESSES);
PERFETTO_DCHECK(!cur_ps_tree_);
CacheProcFsScanStartTimestamp();
base::ScopedDir proc_dir = OpenProcDir();
if (!proc_dir)
return;
base::FlatSet<int32_t> pids;
while (int32_t pid = ReadNextNumericDir(*proc_dir)) {
WriteProcessOrThread(pid);
base::StackString<128> task_path("/proc/%d/task", pid);
base::ScopedDir task_dir(opendir(task_path.c_str()));
if (!task_dir)
continue;
while (int32_t tid = ReadNextNumericDir(*task_dir)) {
if (tid == pid)
continue;
if (record_thread_names_) {
WriteProcessOrThread(tid);
} else {
// If we are not interested in thread names, there is no need to open
// a proc file for each thread. We can save time and directly write the
// thread record. Note that we still read proc_status for recording
// NSpid entries.
std::string proc_status = ReadProcPidFile(tid, "status");
WriteThread(tid, pid, /*optional_name=*/nullptr, proc_status);
}
}
pids.insert(pid);
}
FinalizeCurPacket();
// Also collect any fds open when starting up
for (const auto pid : pids) {
cur_ps_stats_process_ = nullptr;
WriteFds(pid);
}
FinalizeCurPacket();
}
void ProcessStatsDataSource::OnPids(const base::FlatSet<int32_t>& pids) {
if (!enable_on_demand_dumps_)
return;
WriteProcessTree(pids);
}
void ProcessStatsDataSource::WriteProcessTree(
const base::FlatSet<int32_t>& pids) {
PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_ON_PIDS);
PERFETTO_DCHECK(!cur_ps_tree_);
int pids_scanned = 0;
for (int32_t pid : pids) {
if (seen_pids_.count(pid) || pid == 0)
continue;
WriteProcessOrThread(pid);
pids_scanned++;
}
FinalizeCurPacket();
PERFETTO_METATRACE_COUNTER(TAG_PROC_POLLERS, PS_PIDS_SCANNED, pids_scanned);
}
void ProcessStatsDataSource::OnRenamePids(const base::FlatSet<int32_t>& pids) {
PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_ON_RENAME_PIDS);
if (!enable_on_demand_dumps_)
return;
PERFETTO_DCHECK(!cur_ps_tree_);
for (int32_t pid : pids)
seen_pids_.erase(pid);
}
void ProcessStatsDataSource::OnFds(
const base::FlatSet<std::pair<pid_t, uint64_t>>& fds) {
if (!resolve_process_fds_)
return;
pid_t last_pid = 0;
for (const auto& tid_fd : fds) {
const auto tid = tid_fd.first;
const auto fd = tid_fd.second;
auto it = seen_pids_.find(tid);
if (it == seen_pids_.end()) {
// TID is not known yet, skip resolving the fd and let the
// periodic stats scanner resolve the fd together with its TID later
continue;
}
const auto pid = it->tgid;
if (last_pid != pid) {
cur_ps_stats_process_ = nullptr;
last_pid = pid;
}
WriteSingleFd(pid, fd);
}
FinalizeCurPacket();
}
void ProcessStatsDataSource::Flush(FlushRequestID,
std::function<void()> callback) {
// We shouldn't get this in the middle of WriteAllProcesses() or OnPids().
PERFETTO_DCHECK(!cur_ps_tree_);
PERFETTO_DCHECK(!cur_ps_stats_);
PERFETTO_DCHECK(!cur_ps_stats_process_);
writer_->Flush(callback);
}
void ProcessStatsDataSource::WriteProcessOrThread(int32_t pid) {
// In case we're called from outside WriteAllProcesses()
CacheProcFsScanStartTimestamp();
std::string proc_status = ReadProcPidFile(pid, "status");
if (proc_status.empty())
return;
int tgid = ToInt(ReadProcStatusEntry(proc_status, "Tgid:"));
int tid = ToInt(ReadProcStatusEntry(proc_status, "Pid:"));
if (tgid <= 0 || tid <= 0)
return;
if (!seen_pids_.count(tgid)) {
// We need to read the status file if |pid| is non-main thread.
const std::string& proc_status_tgid =
(tgid == tid ? proc_status : ReadProcPidFile(tgid, "status"));
WriteProcess(tgid, proc_status_tgid);
}
if (pid != tgid) {
PERFETTO_DCHECK(!seen_pids_.count(pid));
std::string thread_name;
if (record_thread_names_)
thread_name = ReadProcStatusEntry(proc_status, "Name:");
WriteThread(pid, tgid, thread_name.empty() ? nullptr : thread_name.c_str(),
proc_status);
}
}
void ProcessStatsDataSource::ReadNamespacedTids(int32_t tid,
const std::string& proc_status,
TidArray& out) {
// If a process has entered a PID namespace, NSpid shows the mapping in
// the status file like: NSpid: 28971 2
// NStgid: 28971 2
// which denotes that the thread (or process) 28971 in the root PID namespace
// has PID = 2 in the child PID namespace. This information can be read from
// the NSpid entry in /proc/<tid>/status.
if (proc_status.empty())
return;
std::string nspid = ReadProcStatusEntry(proc_status, "NSpid:");
if (nspid.empty())
return;
out.fill(0); // Zero-initialize the array in case the caller doesn't.
auto it = out.begin();
base::StringSplitter ss(std::move(nspid), '\t');
ss.Next(); // Skip the 1st element.
PERFETTO_DCHECK(base::CStringToInt32(ss.cur_token()) == tid);
while (ss.Next()) {
PERFETTO_CHECK(it < out.end());
auto maybe_int32 = base::CStringToInt32(ss.cur_token());
PERFETTO_DCHECK(maybe_int32.has_value());
*it++ = *maybe_int32;
}
}
void ProcessStatsDataSource::WriteProcess(int32_t pid,
const std::string& proc_status) {
PERFETTO_DCHECK(ToInt(ReadProcStatusEntry(proc_status, "Tgid:")) == pid);
// Assert that |proc_status| is not for a non-main thread.
PERFETTO_DCHECK(ToInt(ReadProcStatusEntry(proc_status, "Pid:")) == pid);
auto* proc = GetOrCreatePsTree()->add_processes();
proc->set_pid(pid);
proc->set_ppid(ToInt(ReadProcStatusEntry(proc_status, "PPid:")));
// Uid will have multiple entries, only return first (real uid).
proc->set_uid(ToInt(ReadProcStatusEntry(proc_status, "Uid:")));
// Optionally write namespace-local PIDs.
TidArray nspids = {};
ReadNamespacedTids(pid, proc_status, nspids);
for (auto nspid : nspids) {
if (nspid == 0) // No more elements.
break;
proc->add_nspid(nspid);
}
std::string cmdline = ReadProcPidFile(pid, "cmdline");
if (!cmdline.empty()) {
if (cmdline.back() != '\0') {
// Some kernels can miss the NUL terminator due to a bug. b/147438623.
cmdline.push_back('\0');
}
using base::StringSplitter;
for (StringSplitter ss(&cmdline[0], cmdline.size(), '\0'); ss.Next();)
proc->add_cmdline(ss.cur_token());
} else {
// Nothing in cmdline so use the thread name instead (which is == "comm").
proc->add_cmdline(ReadProcStatusEntry(proc_status, "Name:").c_str());
}
seen_pids_.insert({pid, pid});
}
void ProcessStatsDataSource::WriteThread(int32_t tid,
int32_t tgid,
const char* optional_name,
const std::string& proc_status) {
auto* thread = GetOrCreatePsTree()->add_threads();
thread->set_tid(tid);
thread->set_tgid(tgid);
if (optional_name)
thread->set_name(optional_name);
// Optionally write namespace-local TIDs.
TidArray nstids = {};
ReadNamespacedTids(tid, proc_status, nstids);
for (auto nstid : nstids) {
if (nstid == 0) // No more elements.
break;
thread->add_nstid(nstid);
}
seen_pids_.insert({tid, tgid});
}
const char* ProcessStatsDataSource::GetProcMountpoint() {
static constexpr char kDefaultProcMountpoint[] = "/proc";
return kDefaultProcMountpoint;
}
base::ScopedDir ProcessStatsDataSource::OpenProcDir() {
base::ScopedDir proc_dir(opendir(GetProcMountpoint()));
if (!proc_dir)
PERFETTO_PLOG("Failed to opendir(%s)", GetProcMountpoint());
return proc_dir;
}
std::string ProcessStatsDataSource::ReadProcPidFile(int32_t pid,
const std::string& file) {
base::StackString<128> path("/proc/%" PRId32 "/%s", pid, file.c_str());
std::string contents;
contents.reserve(4096);
if (!base::ReadFile(path.c_str(), &contents))
return "";
return contents;
}
std::string ProcessStatsDataSource::ReadProcStatusEntry(const std::string& buf,
const char* key) {
auto begin = buf.find(key);
if (begin == std::string::npos)
return "";
begin = buf.find_first_not_of(" \t", begin + strlen(key));
if (begin == std::string::npos)
return "";
auto end = buf.find('\n', begin);
if (end == std::string::npos || end <= begin)
return "";
return buf.substr(begin, end - begin);
}
void ProcessStatsDataSource::StartNewPacketIfNeeded() {
if (cur_packet_)
return;
cur_packet_ = writer_->NewTracePacket();
cur_packet_->set_timestamp(CacheProcFsScanStartTimestamp());
if (did_clear_incremental_state_) {
cur_packet_->set_incremental_state_cleared(true);
did_clear_incremental_state_ = false;
}
}
protos::pbzero::ProcessTree* ProcessStatsDataSource::GetOrCreatePsTree() {
StartNewPacketIfNeeded();
if (!cur_ps_tree_)
cur_ps_tree_ = cur_packet_->set_process_tree();
cur_ps_stats_ = nullptr;
cur_ps_stats_process_ = nullptr;
return cur_ps_tree_;
}
protos::pbzero::ProcessStats* ProcessStatsDataSource::GetOrCreateStats() {
StartNewPacketIfNeeded();
if (!cur_ps_stats_)
cur_ps_stats_ = cur_packet_->set_process_stats();
cur_ps_tree_ = nullptr;
cur_ps_stats_process_ = nullptr;
return cur_ps_stats_;
}
protos::pbzero::ProcessStats_Process*
ProcessStatsDataSource::GetOrCreateStatsProcess(int32_t pid) {
if (cur_ps_stats_process_)
return cur_ps_stats_process_;
cur_ps_stats_process_ = GetOrCreateStats()->add_processes();
cur_ps_stats_process_->set_pid(pid);
return cur_ps_stats_process_;
}
void ProcessStatsDataSource::FinalizeCurPacket() {
PERFETTO_DCHECK(!cur_ps_tree_ || cur_packet_);
PERFETTO_DCHECK(!cur_ps_stats_ || cur_packet_);
uint64_t now = static_cast<uint64_t>(base::GetBootTimeNs().count());
if (cur_ps_tree_) {
cur_ps_tree_->set_collection_end_timestamp(now);
cur_ps_tree_ = nullptr;
}
if (cur_ps_stats_) {
cur_ps_stats_->set_collection_end_timestamp(now);
cur_ps_stats_ = nullptr;
}
cur_ps_stats_process_ = nullptr;
cur_procfs_scan_start_timestamp_ = 0;
cur_packet_ = TraceWriter::TracePacketHandle{};
}
// static
void ProcessStatsDataSource::Tick(
base::WeakPtr<ProcessStatsDataSource> weak_this) {
if (!weak_this)
return;
ProcessStatsDataSource& thiz = *weak_this;
uint32_t period_ms = thiz.poll_period_ms_;
uint32_t delay_ms =
period_ms -
static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
thiz.task_runner_->PostDelayedTask(
std::bind(&ProcessStatsDataSource::Tick, weak_this), delay_ms);
thiz.WriteAllProcessStats();
// We clear the cache every process_stats_cache_ttl_ticks_ ticks.
if (++thiz.cache_ticks_ == thiz.process_stats_cache_ttl_ticks_) {
thiz.cache_ticks_ = 0;
thiz.process_stats_cache_.clear();
}
}
void ProcessStatsDataSource::WriteAllProcessStats() {
// TODO(primiano): implement filtering of processes by names.
// TODO(primiano): Have a pid cache to avoid wasting cycles reading kthreads
// proc files over and over. Same for non-filtered processes (see above).
CacheProcFsScanStartTimestamp();
PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_WRITE_ALL_PROCESS_STATS);
base::ScopedDir proc_dir = OpenProcDir();
if (!proc_dir)
return;
base::FlatSet<int32_t> pids;
while (int32_t pid = ReadNextNumericDir(*proc_dir)) {
cur_ps_stats_process_ = nullptr;
uint32_t pid_u = static_cast<uint32_t>(pid);
if (skip_stats_for_pids_.size() > pid_u && skip_stats_for_pids_[pid_u])
continue;
std::string proc_status = ReadProcPidFile(pid, "status");
if (proc_status.empty())
continue;
if (!WriteMemCounters(pid, proc_status)) {
// If WriteMemCounters() fails the pid is very likely a kernel thread
// that has a valid /proc/[pid]/status but no memory values. In this
// case avoid keep polling it over and over.
if (skip_stats_for_pids_.size() <= pid_u)
skip_stats_for_pids_.resize(pid_u + 1);
skip_stats_for_pids_[pid_u] = true;
continue;
}
std::string oom_score_adj = ReadProcPidFile(pid, "oom_score_adj");
if (!oom_score_adj.empty()) {
CachedProcessStats& cached = process_stats_cache_[pid];
auto counter = ToInt(oom_score_adj);
if (counter != cached.oom_score_adj) {
GetOrCreateStatsProcess(pid)->set_oom_score_adj(counter);
cached.oom_score_adj = counter;
}
}
// Ensure we write data on any fds not seen before
WriteFds(pid);
pids.insert(pid);
}
FinalizeCurPacket();
// Ensure that we write once long-term process info (e.g., name) for new pids
// that we haven't seen before.
WriteProcessTree(pids);
}
// Returns true if the stats for the given |pid| have been written, false it
// it failed (e.g., |pid| was a kernel thread and, as such, didn't report any
// memory counters).
bool ProcessStatsDataSource::WriteMemCounters(int32_t pid,
const std::string& proc_status) {
bool proc_status_has_mem_counters = false;
CachedProcessStats& cached = process_stats_cache_[pid];
// Parse /proc/[pid]/status, which looks like this:
// Name: cat
// Umask: 0027
// State: R (running)
// FDSize: 256
// Groups: 4 20 24 46 997
// VmPeak: 5992 kB
// VmSize: 5992 kB
// VmLck: 0 kB
// ...
std::vector<char> key;
std::vector<char> value;
enum { kKey, kSeparator, kValue } state = kKey;
for (char c : proc_status) {
if (c == '\n') {
key.push_back('\0');
value.push_back('\0');
// |value| will contain "1234 KB". We rely on strtol() (in ToU32()) to
// stop parsing at the first non-numeric character.
if (strcmp(key.data(), "VmSize") == 0) {
// Assume that if we see VmSize we'll see also the others.
proc_status_has_mem_counters = true;
auto counter = ToU32(value.data());
if (counter != cached.vm_size_kb) {
GetOrCreateStatsProcess(pid)->set_vm_size_kb(counter);
cached.vm_size_kb = counter;
}
} else if (strcmp(key.data(), "VmLck") == 0) {
auto counter = ToU32(value.data());
if (counter != cached.vm_locked_kb) {
GetOrCreateStatsProcess(pid)->set_vm_locked_kb(counter);
cached.vm_locked_kb = counter;
}
} else if (strcmp(key.data(), "VmHWM") == 0) {
auto counter = ToU32(value.data());
if (counter != cached.vm_hvm_kb) {
GetOrCreateStatsProcess(pid)->set_vm_hwm_kb(counter);
cached.vm_hvm_kb = counter;
}
} else if (strcmp(key.data(), "VmRSS") == 0) {
auto counter = ToU32(value.data());
if (counter != cached.vm_rss_kb) {
GetOrCreateStatsProcess(pid)->set_vm_rss_kb(counter);
cached.vm_rss_kb = counter;
}
} else if (strcmp(key.data(), "RssAnon") == 0) {
auto counter = ToU32(value.data());
if (counter != cached.rss_anon_kb) {
GetOrCreateStatsProcess(pid)->set_rss_anon_kb(counter);
cached.rss_anon_kb = counter;
}
} else if (strcmp(key.data(), "RssFile") == 0) {
auto counter = ToU32(value.data());
if (counter != cached.rss_file_kb) {
GetOrCreateStatsProcess(pid)->set_rss_file_kb(counter);
cached.rss_file_kb = counter;
}
} else if (strcmp(key.data(), "RssShmem") == 0) {
auto counter = ToU32(value.data());
if (counter != cached.rss_shmem_kb) {
GetOrCreateStatsProcess(pid)->set_rss_shmem_kb(counter);
cached.rss_shmem_kb = counter;
}
} else if (strcmp(key.data(), "VmSwap") == 0) {
auto counter = ToU32(value.data());
if (counter != cached.vm_swap_kb) {
GetOrCreateStatsProcess(pid)->set_vm_swap_kb(counter);
cached.vm_swap_kb = counter;
}
}
key.clear();
state = kKey;
continue;
}
if (state == kKey) {
if (c == ':') {
state = kSeparator;
continue;
}
key.push_back(c);
continue;
}
if (state == kSeparator) {
if (isspace(c))
continue;
value.clear();
value.push_back(c);
state = kValue;
continue;
}
if (state == kValue) {
value.push_back(c);
}
}
return proc_status_has_mem_counters;
}
void ProcessStatsDataSource::WriteFds(int32_t pid) {
if (!resolve_process_fds_) {
return;
}
base::StackString<256> path("%s/%" PRId32 "/fd", GetProcMountpoint(), pid);
base::ScopedDir proc_dir(opendir(path.c_str()));
if (!proc_dir) {
PERFETTO_DPLOG("Failed to opendir(%s)", path.c_str());
return;
}
while (struct dirent* dir_ent = readdir(*proc_dir)) {
if (dir_ent->d_type != DT_LNK)
continue;
auto fd = base::CStringToUInt64(dir_ent->d_name);
if (fd)
WriteSingleFd(pid, *fd);
}
}
void ProcessStatsDataSource::WriteSingleFd(int32_t pid, uint64_t fd) {
CachedProcessStats& cached = process_stats_cache_[pid];
if (cached.seen_fds.count(fd)) {
return;
}
base::StackString<128> proc_fd("%s/%" PRId32 "/fd/%" PRIu64,
GetProcMountpoint(), pid, fd);
std::array<char, 256> path;
ssize_t actual = readlink(proc_fd.c_str(), path.data(), path.size());
if (actual >= 0) {
auto* fd_info = GetOrCreateStatsProcess(pid)->add_fds();
fd_info->set_fd(fd);
fd_info->set_path(path.data(), static_cast<size_t>(actual));
cached.seen_fds.insert(fd);
} else if (ENOENT != errno) {
PERFETTO_DPLOG("Failed to readlink '%s'", proc_fd.c_str());
}
}
uint64_t ProcessStatsDataSource::CacheProcFsScanStartTimestamp() {
if (!cur_procfs_scan_start_timestamp_)
cur_procfs_scan_start_timestamp_ =
static_cast<uint64_t>(base::GetBootTimeNs().count());
return cur_procfs_scan_start_timestamp_;
}
void ProcessStatsDataSource::ClearIncrementalState() {
PERFETTO_DLOG("ProcessStatsDataSource clearing incremental state.");
seen_pids_.clear();
skip_stats_for_pids_.clear();
cache_ticks_ = 0;
process_stats_cache_.clear();
// Set the relevant flag in the next packet.
did_clear_incremental_state_ = true;
}
} // namespace perfetto