1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/traced/probes/ps/process_stats_data_source.h"
18
19 #include <stdlib.h>
20 #include <unistd.h>
21
22 #include <algorithm>
23 #include <array>
24 #include <optional>
25
26 #include "perfetto/base/task_runner.h"
27 #include "perfetto/base/time.h"
28 #include "perfetto/ext/base/file_utils.h"
29 #include "perfetto/ext/base/metatrace.h"
30 #include "perfetto/ext/base/scoped_file.h"
31 #include "perfetto/ext/base/string_splitter.h"
32 #include "perfetto/ext/base/string_utils.h"
33 #include "perfetto/tracing/core/data_source_config.h"
34
35 #include "protos/perfetto/config/process_stats/process_stats_config.pbzero.h"
36 #include "protos/perfetto/trace/ps/process_stats.pbzero.h"
37 #include "protos/perfetto/trace/ps/process_tree.pbzero.h"
38 #include "protos/perfetto/trace/trace_packet.pbzero.h"
39
40 // The notion of PID in the Linux kernel is a bit confusing.
41 // - PID: is really the thread id (for the main thread: PID == TID).
42 // - TGID (thread group ID): is the Unix Process ID (the actual PID).
43 // - PID == TGID for the main thread: the TID of the main thread is also the PID
44 // of the process.
45 // So, in this file, |pid| might refer to either a process id or a thread id.
46
47 // Dealing with PID reuse: the knowledge of which PIDs were already scraped is
48 // forgotten on every |ClearIncrementalState| if the trace config sets
49 // |incremental_state_config|. Additionally, there's a proactive invalidation
50 // whenever we see a task rename ftrace event, as that's a good signal that the
51 // /proc/pid/cmdline needs updating.
52 // TODO(rsavitski): consider invalidating on task creation or death ftrace
53 // events if available.
54 //
55 // TODO(rsavitski): we're not emitting an explicit description of the main
56 // thread (instead, it's implied by the process entry). This might be slightly
57 // inaccurate in edge cases like wanting to know the primary thread's name
58 // (comm) based on procfs alone.
59
60 namespace perfetto {
61 namespace {
62
ReadNextNumericDir(DIR * dirp)63 int32_t ReadNextNumericDir(DIR* dirp) {
64 while (struct dirent* dir_ent = readdir(dirp)) {
65 if (dir_ent->d_type != DT_DIR)
66 continue;
67 auto int_value = base::CStringToInt32(dir_ent->d_name);
68 if (int_value)
69 return *int_value;
70 }
71 return 0;
72 }
73
ProcStatusEntry(const std::string & buf,const char * key)74 std::string ProcStatusEntry(const std::string& buf, const char* key) {
75 auto begin = buf.find(key);
76 if (begin == std::string::npos)
77 return "";
78 begin = buf.find_first_not_of(" \t", begin + strlen(key));
79 if (begin == std::string::npos)
80 return "";
81 auto end = buf.find('\n', begin);
82 if (end == std::string::npos || end <= begin)
83 return "";
84 return buf.substr(begin, end - begin);
85 }
86
87 // Parses out the thread IDs in each non-root PID namespace from
88 // /proc/tid/status. Returns true if there is at least one non-root PID
89 // namespace.
90 template <typename Callback>
ParseNamespacedTids(const std::string & proc_status,Callback callback)91 bool ParseNamespacedTids(const std::string& proc_status, Callback callback) {
92 std::string str = ProcStatusEntry(proc_status, "NSpid:");
93 if (str.empty())
94 return false;
95
96 base::StringSplitter ss(std::move(str), '\t');
97 ss.Next(); // first element = root tid that we already know
98 bool namespaced = false;
99 while (ss.Next()) {
100 namespaced = true;
101 std::optional<int32_t> nstid = base::CStringToInt32(ss.cur_token());
102 PERFETTO_DCHECK(nstid.has_value());
103 callback(nstid.value_or(0));
104 }
105 return namespaced;
106 }
107
108 struct ProcessRuntimes {
109 uint64_t utime = 0;
110 uint64_t stime = 0;
111 uint64_t starttime = 0;
112 };
113
ParseProcessRuntimes(const std::string & proc_stat)114 std::optional<ProcessRuntimes> ParseProcessRuntimes(
115 const std::string& proc_stat) {
116 // /proc/pid/stat fields of interest, counting from 1:
117 // utime = 14
118 // stime = 15
119 // starttime = 22
120 // sscanf format string below is formatted to 10 values per line.
121 // clang-format off
122 ProcessRuntimes ret = {};
123 if (sscanf(proc_stat.c_str(),
124 "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u "
125 "%*u %*u %*u %" SCNu64 " %" SCNu64 " %*d %*d %*d %*d %*d "
126 "%*d %" SCNu64 "",
127 &ret.utime, &ret.stime, &ret.starttime) != 3) {
128 PERFETTO_DLOG("empty or unexpected /proc/pid/stat contents");
129 return std::nullopt;
130 }
131 // clang-format on
132 int64_t tickrate = sysconf(_SC_CLK_TCK);
133 if (tickrate <= 0)
134 return std::nullopt;
135 uint64_t ns_per_tick = 1'000'000'000ULL / static_cast<uint64_t>(tickrate);
136
137 ret.utime *= ns_per_tick;
138 ret.stime *= ns_per_tick;
139 ret.starttime *= ns_per_tick;
140 return ret;
141 }
142
143 // Note: conversions intentionally not checking that the full string was
144 // numerical as calling code depends on discarding suffixes in cases such as:
145 // * "92 kB" -> 92
146 // * "1000 2000" -> 1000
ToInt32(const std::string & str)147 inline int32_t ToInt32(const std::string& str) {
148 return static_cast<int32_t>(strtol(str.c_str(), nullptr, 10));
149 }
150
ToUInt32(const char * str)151 inline uint32_t ToUInt32(const char* str) {
152 return static_cast<uint32_t>(strtoul(str, nullptr, 10));
153 }
154
155 } // namespace
156
157 // static
158 const ProbesDataSource::Descriptor ProcessStatsDataSource::descriptor = {
159 /*name*/ "linux.process_stats",
160 /*flags*/ Descriptor::kHandlesIncrementalState,
161 /*fill_descriptor_func*/ nullptr,
162 };
163
ProcessStatsDataSource(base::TaskRunner * task_runner,TracingSessionID session_id,std::unique_ptr<TraceWriter> writer,const DataSourceConfig & ds_config)164 ProcessStatsDataSource::ProcessStatsDataSource(
165 base::TaskRunner* task_runner,
166 TracingSessionID session_id,
167 std::unique_ptr<TraceWriter> writer,
168 const DataSourceConfig& ds_config)
169 : ProbesDataSource(session_id, &descriptor),
170 task_runner_(task_runner),
171 writer_(std::move(writer)),
172 weak_factory_(this) {
173 using protos::pbzero::ProcessStatsConfig;
174 ProcessStatsConfig::Decoder cfg(ds_config.process_stats_config_raw());
175 record_thread_names_ = cfg.record_thread_names();
176 dump_all_procs_on_start_ = cfg.scan_all_processes_on_start();
177 resolve_process_fds_ = cfg.resolve_process_fds();
178 scan_smaps_rollup_ = cfg.scan_smaps_rollup();
179 record_process_age_ = cfg.record_process_age();
180 record_process_runtime_ = cfg.record_process_runtime();
181
182 enable_on_demand_dumps_ = true;
183 for (auto quirk = cfg.quirks(); quirk; ++quirk) {
184 if (*quirk == ProcessStatsConfig::DISABLE_ON_DEMAND)
185 enable_on_demand_dumps_ = false;
186 }
187
188 poll_period_ms_ = cfg.proc_stats_poll_ms();
189 if (poll_period_ms_ > 0 && poll_period_ms_ < 100) {
190 PERFETTO_ILOG("proc_stats_poll_ms %" PRIu32
191 " is less than minimum of 100ms. Increasing to 100ms.",
192 poll_period_ms_);
193 poll_period_ms_ = 100;
194 }
195
196 if (poll_period_ms_ > 0) {
197 auto proc_stats_ttl_ms = cfg.proc_stats_cache_ttl_ms();
198 process_stats_cache_ttl_ticks_ =
199 std::max(proc_stats_ttl_ms / poll_period_ms_, 1u);
200 }
201 }
202
203 ProcessStatsDataSource::~ProcessStatsDataSource() = default;
204
Start()205 void ProcessStatsDataSource::Start() {
206 if (dump_all_procs_on_start_) {
207 WriteAllProcesses();
208 }
209
210 if (poll_period_ms_) {
211 auto weak_this = GetWeakPtr();
212 task_runner_->PostTask(std::bind(&ProcessStatsDataSource::Tick, weak_this));
213 }
214 }
215
GetWeakPtr() const216 base::WeakPtr<ProcessStatsDataSource> ProcessStatsDataSource::GetWeakPtr()
217 const {
218 return weak_factory_.GetWeakPtr();
219 }
220
WriteAllProcesses()221 void ProcessStatsDataSource::WriteAllProcesses() {
222 PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_WRITE_ALL_PROCESSES);
223 PERFETTO_DCHECK(!cur_ps_tree_);
224
225 CacheProcFsScanStartTimestamp();
226
227 base::ScopedDir proc_dir = OpenProcDir();
228 if (!proc_dir)
229 return;
230 base::FlatSet<int32_t> pids;
231 while (int32_t pid = ReadNextNumericDir(*proc_dir)) {
232 std::string pid_status = ReadProcPidFile(pid, "status");
233 std::string pid_stat =
234 record_process_age_ ? ReadProcPidFile(pid, "stat") : "";
235 bool namespaced_process = WriteProcess(pid, pid_status, pid_stat);
236
237 base::StackString<128> task_path("/proc/%d/task", pid);
238 base::ScopedDir task_dir(opendir(task_path.c_str()));
239 if (!task_dir)
240 continue;
241
242 while (int32_t tid = ReadNextNumericDir(*task_dir)) {
243 if (tid == pid)
244 continue;
245 if (record_thread_names_ || namespaced_process) {
246 std::string tid_status = ReadProcPidFile(tid, "status");
247 WriteDetailedThread(tid, pid, tid_status);
248 } else {
249 WriteThread(tid, pid);
250 }
251 }
252
253 pids.insert(pid);
254 }
255 FinalizeCurPacket();
256
257 // Also collect any fds open when starting up (niche option).
258 for (const auto pid : pids) {
259 cur_ps_stats_process_ = nullptr;
260 WriteFds(pid);
261 }
262 FinalizeCurPacket();
263 }
264
OnPids(const base::FlatSet<int32_t> & pids)265 void ProcessStatsDataSource::OnPids(const base::FlatSet<int32_t>& pids) {
266 if (!enable_on_demand_dumps_)
267 return;
268 WriteProcessTree(pids);
269 }
270
WriteProcessTree(const base::FlatSet<int32_t> & pids)271 void ProcessStatsDataSource::WriteProcessTree(
272 const base::FlatSet<int32_t>& pids) {
273 PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_ON_PIDS);
274 PERFETTO_DCHECK(!cur_ps_tree_);
275 int pids_scanned = 0;
276 for (int32_t pid : pids) {
277 if (seen_pids_.count(pid) || pid == 0)
278 continue;
279 WriteProcessOrThread(pid);
280 pids_scanned++;
281 }
282 FinalizeCurPacket();
283 PERFETTO_METATRACE_COUNTER(TAG_PROC_POLLERS, PS_PIDS_SCANNED, pids_scanned);
284 }
285
OnRenamePids(const base::FlatSet<int32_t> & pids)286 void ProcessStatsDataSource::OnRenamePids(const base::FlatSet<int32_t>& pids) {
287 PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_ON_RENAME_PIDS);
288 if (!enable_on_demand_dumps_)
289 return;
290 PERFETTO_DCHECK(!cur_ps_tree_);
291 for (int32_t pid : pids)
292 seen_pids_.erase(pid);
293 }
294
OnFds(const base::FlatSet<std::pair<pid_t,uint64_t>> & fds)295 void ProcessStatsDataSource::OnFds(
296 const base::FlatSet<std::pair<pid_t, uint64_t>>& fds) {
297 if (!resolve_process_fds_)
298 return;
299
300 pid_t last_pid = 0;
301 for (const auto& tid_fd : fds) {
302 const auto tid = tid_fd.first;
303 const auto fd = tid_fd.second;
304
305 auto it = seen_pids_.find(tid);
306 if (it == seen_pids_.end()) {
307 // TID is not known yet, skip resolving the fd and let the
308 // periodic stats scanner resolve the fd together with its TID later
309 continue;
310 }
311 const auto pid = it->tgid;
312
313 if (last_pid != pid) {
314 cur_ps_stats_process_ = nullptr;
315 last_pid = pid;
316 }
317 WriteSingleFd(pid, fd);
318 }
319 FinalizeCurPacket();
320 }
321
Flush(FlushRequestID,std::function<void ()> callback)322 void ProcessStatsDataSource::Flush(FlushRequestID,
323 std::function<void()> callback) {
324 // We shouldn't get this in the middle of WriteAllProcesses() or OnPids().
325 PERFETTO_DCHECK(!cur_ps_tree_);
326 PERFETTO_DCHECK(!cur_ps_stats_);
327 PERFETTO_DCHECK(!cur_ps_stats_process_);
328 writer_->Flush(callback);
329 }
330
WriteProcessOrThread(int32_t pid)331 void ProcessStatsDataSource::WriteProcessOrThread(int32_t pid) {
332 // In case we're called from outside WriteAllProcesses()
333 CacheProcFsScanStartTimestamp();
334
335 std::string proc_status = ReadProcPidFile(pid, "status");
336 if (proc_status.empty())
337 return;
338 int32_t tgid = ToInt32(ProcStatusEntry(proc_status, "Tgid:"));
339 int32_t tid = ToInt32(ProcStatusEntry(proc_status, "Pid:"));
340 if (tgid <= 0 || tid <= 0)
341 return;
342
343 if (!seen_pids_.count(tgid)) {
344 // We need to read the status file if |pid| is non-main thread.
345 const std::string& proc_status_tgid =
346 (tgid == tid ? proc_status : ReadProcPidFile(tgid, "status"));
347 const std::string& proc_stat =
348 record_process_age_ ? ReadProcPidFile(tgid, "stat") : "";
349 WriteProcess(tgid, proc_status_tgid, proc_stat);
350 }
351 if (pid != tgid) {
352 PERFETTO_DCHECK(!seen_pids_.count(pid));
353 WriteDetailedThread(pid, tgid, proc_status);
354 }
355 }
356
357 // Returns true if the process is within a PID namespace.
WriteProcess(int32_t pid,const std::string & proc_status,const std::string & proc_stat)358 bool ProcessStatsDataSource::WriteProcess(int32_t pid,
359 const std::string& proc_status,
360 const std::string& proc_stat) {
361 PERFETTO_DCHECK(ToInt32(ProcStatusEntry(proc_status, "Pid:")) == pid);
362
363 // pid might've been reused for a non-main thread before our procfs read
364 if (PERFETTO_UNLIKELY(pid != ToInt32(ProcStatusEntry(proc_status, "Tgid:"))))
365 return false;
366
367 protos::pbzero::ProcessTree::Process* proc =
368 GetOrCreatePsTree()->add_processes();
369 proc->set_pid(pid);
370 proc->set_ppid(ToInt32(ProcStatusEntry(proc_status, "PPid:")));
371 // Uid will have multiple entries, only return first (real uid).
372 proc->set_uid(ToInt32(ProcStatusEntry(proc_status, "Uid:")));
373 bool namespaced = ParseNamespacedTids(
374 proc_status, [proc](int32_t nspid) { proc->add_nspid(nspid); });
375
376 std::string cmdline = ReadProcPidFile(pid, "cmdline");
377 if (!cmdline.empty()) {
378 if (cmdline.back() != '\0') {
379 // Some kernels can miss the NUL terminator due to a bug. b/147438623.
380 cmdline.push_back('\0');
381 }
382 for (base::StringSplitter ss(cmdline.data(), cmdline.size(), '\0');
383 ss.Next();) {
384 proc->add_cmdline(ss.cur_token());
385 }
386 } else {
387 // Nothing in cmdline so use the thread name instead (which is == "comm").
388 proc->add_cmdline(ProcStatusEntry(proc_status, "Name:"));
389 }
390
391 if (record_process_age_ && !proc_stat.empty()) {
392 std::optional<ProcessRuntimes> times = ParseProcessRuntimes(proc_stat);
393 if (times.has_value()) {
394 proc->set_process_start_from_boot(times->starttime);
395 }
396 }
397
398 seen_pids_.insert({pid, pid});
399 return namespaced;
400 }
401
WriteThread(int32_t tid,int32_t tgid)402 void ProcessStatsDataSource::WriteThread(int32_t tid, int32_t tgid) {
403 auto* thread = GetOrCreatePsTree()->add_threads();
404 thread->set_tid(tid);
405 thread->set_tgid(tgid);
406 seen_pids_.insert({tid, tgid});
407 }
408
409 // Emit thread proto that requires /proc/tid/status contents. May also be called
410 // from places where the proc status contents are already available, but might
411 // end up unused.
WriteDetailedThread(int32_t tid,int32_t tgid,const std::string & proc_status)412 void ProcessStatsDataSource::WriteDetailedThread(
413 int32_t tid,
414 int32_t tgid,
415 const std::string& proc_status) {
416 auto* thread = GetOrCreatePsTree()->add_threads();
417 thread->set_tid(tid);
418 thread->set_tgid(tgid);
419
420 ParseNamespacedTids(proc_status,
421 [thread](int32_t nstid) { thread->add_nstid(nstid); });
422
423 if (record_thread_names_) {
424 std::string thread_name = ProcStatusEntry(proc_status, "Name:");
425 thread->set_name(thread_name);
426 }
427 seen_pids_.insert({tid, tgid});
428 }
429
GetProcMountpoint()430 const char* ProcessStatsDataSource::GetProcMountpoint() {
431 static constexpr char kDefaultProcMountpoint[] = "/proc";
432 return kDefaultProcMountpoint;
433 }
434
OpenProcDir()435 base::ScopedDir ProcessStatsDataSource::OpenProcDir() {
436 base::ScopedDir proc_dir(opendir(GetProcMountpoint()));
437 if (!proc_dir)
438 PERFETTO_PLOG("Failed to opendir(%s)", GetProcMountpoint());
439 return proc_dir;
440 }
441
ReadProcPidFile(int32_t pid,const std::string & file)442 std::string ProcessStatsDataSource::ReadProcPidFile(int32_t pid,
443 const std::string& file) {
444 base::StackString<128> path("/proc/%" PRId32 "/%s", pid, file.c_str());
445 std::string contents;
446 contents.reserve(4096);
447 if (!base::ReadFile(path.c_str(), &contents))
448 return "";
449 return contents;
450 }
451
StartNewPacketIfNeeded()452 void ProcessStatsDataSource::StartNewPacketIfNeeded() {
453 if (cur_packet_)
454 return;
455 cur_packet_ = writer_->NewTracePacket();
456 cur_packet_->set_timestamp(CacheProcFsScanStartTimestamp());
457
458 if (did_clear_incremental_state_) {
459 cur_packet_->set_incremental_state_cleared(true);
460 did_clear_incremental_state_ = false;
461 }
462 }
463
GetOrCreatePsTree()464 protos::pbzero::ProcessTree* ProcessStatsDataSource::GetOrCreatePsTree() {
465 StartNewPacketIfNeeded();
466 if (!cur_ps_tree_)
467 cur_ps_tree_ = cur_packet_->set_process_tree();
468 cur_ps_stats_ = nullptr;
469 cur_ps_stats_process_ = nullptr;
470 return cur_ps_tree_;
471 }
472
GetOrCreateStats()473 protos::pbzero::ProcessStats* ProcessStatsDataSource::GetOrCreateStats() {
474 StartNewPacketIfNeeded();
475 if (!cur_ps_stats_)
476 cur_ps_stats_ = cur_packet_->set_process_stats();
477 cur_ps_tree_ = nullptr;
478 cur_ps_stats_process_ = nullptr;
479 return cur_ps_stats_;
480 }
481
482 protos::pbzero::ProcessStats_Process*
GetOrCreateStatsProcess(int32_t pid)483 ProcessStatsDataSource::GetOrCreateStatsProcess(int32_t pid) {
484 if (cur_ps_stats_process_)
485 return cur_ps_stats_process_;
486 cur_ps_stats_process_ = GetOrCreateStats()->add_processes();
487 cur_ps_stats_process_->set_pid(pid);
488 return cur_ps_stats_process_;
489 }
490
FinalizeCurPacket()491 void ProcessStatsDataSource::FinalizeCurPacket() {
492 PERFETTO_DCHECK(!cur_ps_tree_ || cur_packet_);
493 PERFETTO_DCHECK(!cur_ps_stats_ || cur_packet_);
494 uint64_t now = static_cast<uint64_t>(base::GetBootTimeNs().count());
495 if (cur_ps_tree_) {
496 cur_ps_tree_->set_collection_end_timestamp(now);
497 cur_ps_tree_ = nullptr;
498 }
499 if (cur_ps_stats_) {
500 cur_ps_stats_->set_collection_end_timestamp(now);
501 cur_ps_stats_ = nullptr;
502 }
503 cur_ps_stats_process_ = nullptr;
504 cur_procfs_scan_start_timestamp_ = 0;
505 cur_packet_ = TraceWriter::TracePacketHandle{};
506 }
507
508 // static
Tick(base::WeakPtr<ProcessStatsDataSource> weak_this)509 void ProcessStatsDataSource::Tick(
510 base::WeakPtr<ProcessStatsDataSource> weak_this) {
511 if (!weak_this)
512 return;
513 ProcessStatsDataSource& thiz = *weak_this;
514 uint32_t period_ms = thiz.poll_period_ms_;
515 uint32_t delay_ms =
516 period_ms -
517 static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
518 thiz.task_runner_->PostDelayedTask(
519 std::bind(&ProcessStatsDataSource::Tick, weak_this), delay_ms);
520 thiz.WriteAllProcessStats();
521
522 // We clear the cache every process_stats_cache_ttl_ticks_ ticks.
523 if (++thiz.cache_ticks_ == thiz.process_stats_cache_ttl_ticks_) {
524 thiz.cache_ticks_ = 0;
525 thiz.process_stats_cache_.clear();
526 }
527 }
528
WriteAllProcessStats()529 void ProcessStatsDataSource::WriteAllProcessStats() {
530 CacheProcFsScanStartTimestamp();
531 PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_WRITE_ALL_PROCESS_STATS);
532 base::ScopedDir proc_dir = OpenProcDir();
533 if (!proc_dir)
534 return;
535 base::FlatSet<int32_t> pids;
536 while (int32_t pid = ReadNextNumericDir(*proc_dir)) {
537 cur_ps_stats_process_ = nullptr;
538 uint32_t pid_u = static_cast<uint32_t>(pid);
539
540 // optional /proc/pid/stat fields
541 if (record_process_runtime_) {
542 std::string proc_stat = ReadProcPidFile(pid, "stat");
543 if (WriteProcessRuntimes(pid, proc_stat)) {
544 pids.insert(pid);
545 }
546 }
547
548 // memory counters
549 if (skip_mem_for_pids_.size() > pid_u && skip_mem_for_pids_[pid_u])
550 continue;
551
552 std::string proc_status = ReadProcPidFile(pid, "status");
553 if (proc_status.empty())
554 continue;
555
556 if (scan_smaps_rollup_) {
557 std::string proc_smaps_rollup = ReadProcPidFile(pid, "smaps_rollup");
558 proc_status.append(proc_smaps_rollup);
559 }
560
561 if (!WriteMemCounters(pid, proc_status)) {
562 // If WriteMemCounters() fails the pid is very likely a kernel thread
563 // that has a valid /proc/[pid]/status but no memory values. In this
564 // case avoid keep polling it over and over.
565 if (skip_mem_for_pids_.size() <= pid_u)
566 skip_mem_for_pids_.resize(pid_u + 1);
567 skip_mem_for_pids_[pid_u] = true;
568 continue;
569 }
570
571 std::string oom_score_adj = ReadProcPidFile(pid, "oom_score_adj");
572 if (!oom_score_adj.empty()) {
573 CachedProcessStats& cached = process_stats_cache_[pid];
574 int32_t counter = ToInt32(oom_score_adj);
575 if (counter != cached.oom_score_adj) {
576 GetOrCreateStatsProcess(pid)->set_oom_score_adj(counter);
577 cached.oom_score_adj = counter;
578 }
579 }
580
581 // Ensure we write data on any fds not seen before (niche option).
582 WriteFds(pid);
583
584 pids.insert(pid);
585 }
586 FinalizeCurPacket();
587
588 // Ensure that we write once long-term process info (e.g., name) for new pids
589 // that we haven't seen before.
590 WriteProcessTree(pids);
591 }
592
WriteProcessRuntimes(int32_t pid,const std::string & proc_stat)593 bool ProcessStatsDataSource::WriteProcessRuntimes(
594 int32_t pid,
595 const std::string& proc_stat) {
596 std::optional<ProcessRuntimes> times = ParseProcessRuntimes(proc_stat);
597 if (!times.has_value())
598 return false;
599
600 CachedProcessStats& cached = process_stats_cache_[pid];
601 if (times->utime != cached.runtime_user_mode_ns) {
602 GetOrCreateStatsProcess(pid)->set_runtime_user_mode(times->utime);
603 cached.runtime_user_mode_ns = times->utime;
604 }
605 if (times->stime != cached.runtime_kernel_mode_ns) {
606 GetOrCreateStatsProcess(pid)->set_runtime_kernel_mode(times->stime);
607 cached.runtime_kernel_mode_ns = times->stime;
608 }
609 return true;
610 }
611
612 // Returns true if the stats for the given |pid| have been written, false it
613 // it failed (e.g., |pid| was a kernel thread and, as such, didn't report any
614 // memory counters).
WriteMemCounters(int32_t pid,const std::string & proc_status)615 bool ProcessStatsDataSource::WriteMemCounters(int32_t pid,
616 const std::string& proc_status) {
617 bool proc_status_has_mem_counters = false;
618 CachedProcessStats& cached = process_stats_cache_[pid];
619
620 // Parse /proc/[pid]/status, which looks like this:
621 // Name: cat
622 // Umask: 0027
623 // State: R (running)
624 // FDSize: 256
625 // Groups: 4 20 24 46 997
626 // VmPeak: 5992 kB
627 // VmSize: 5992 kB
628 // VmLck: 0 kB
629 // ...
630 std::vector<char> key;
631 std::vector<char> value;
632 enum { kKey, kSeparator, kValue } state = kKey;
633 for (char c : proc_status) {
634 if (c == '\n') {
635 key.push_back('\0');
636 value.push_back('\0');
637
638 // |value| will contain "1234 KB". We rely on ToUInt32() to stop parsing
639 // at the first non-numeric character.
640 if (strcmp(key.data(), "VmSize") == 0) {
641 // Assume that if we see VmSize we'll see also the others.
642 proc_status_has_mem_counters = true;
643
644 auto counter = ToUInt32(value.data());
645 if (counter != cached.vm_size_kb) {
646 GetOrCreateStatsProcess(pid)->set_vm_size_kb(counter);
647 cached.vm_size_kb = counter;
648 }
649 } else if (strcmp(key.data(), "VmLck") == 0) {
650 auto counter = ToUInt32(value.data());
651 if (counter != cached.vm_locked_kb) {
652 GetOrCreateStatsProcess(pid)->set_vm_locked_kb(counter);
653 cached.vm_locked_kb = counter;
654 }
655 } else if (strcmp(key.data(), "VmHWM") == 0) {
656 auto counter = ToUInt32(value.data());
657 if (counter != cached.vm_hvm_kb) {
658 GetOrCreateStatsProcess(pid)->set_vm_hwm_kb(counter);
659 cached.vm_hvm_kb = counter;
660 }
661 } else if (strcmp(key.data(), "VmRSS") == 0) {
662 auto counter = ToUInt32(value.data());
663 if (counter != cached.vm_rss_kb) {
664 GetOrCreateStatsProcess(pid)->set_vm_rss_kb(counter);
665 cached.vm_rss_kb = counter;
666 }
667 } else if (strcmp(key.data(), "RssAnon") == 0) {
668 auto counter = ToUInt32(value.data());
669 if (counter != cached.rss_anon_kb) {
670 GetOrCreateStatsProcess(pid)->set_rss_anon_kb(counter);
671 cached.rss_anon_kb = counter;
672 }
673 } else if (strcmp(key.data(), "RssFile") == 0) {
674 auto counter = ToUInt32(value.data());
675 if (counter != cached.rss_file_kb) {
676 GetOrCreateStatsProcess(pid)->set_rss_file_kb(counter);
677 cached.rss_file_kb = counter;
678 }
679 } else if (strcmp(key.data(), "RssShmem") == 0) {
680 auto counter = ToUInt32(value.data());
681 if (counter != cached.rss_shmem_kb) {
682 GetOrCreateStatsProcess(pid)->set_rss_shmem_kb(counter);
683 cached.rss_shmem_kb = counter;
684 }
685 } else if (strcmp(key.data(), "VmSwap") == 0) {
686 auto counter = ToUInt32(value.data());
687 if (counter != cached.vm_swap_kb) {
688 GetOrCreateStatsProcess(pid)->set_vm_swap_kb(counter);
689 cached.vm_swap_kb = counter;
690 }
691 // The entries below come from smaps_rollup, WriteAllProcessStats merges
692 // everything into the same buffer for convenience.
693 } else if (strcmp(key.data(), "Rss") == 0) {
694 auto counter = ToUInt32(value.data());
695 if (counter != cached.smr_rss_kb) {
696 GetOrCreateStatsProcess(pid)->set_smr_rss_kb(counter);
697 cached.smr_rss_kb = counter;
698 }
699 } else if (strcmp(key.data(), "Pss") == 0) {
700 auto counter = ToUInt32(value.data());
701 if (counter != cached.smr_pss_kb) {
702 GetOrCreateStatsProcess(pid)->set_smr_pss_kb(counter);
703 cached.smr_pss_kb = counter;
704 }
705 } else if (strcmp(key.data(), "Pss_Anon") == 0) {
706 auto counter = ToUInt32(value.data());
707 if (counter != cached.smr_pss_anon_kb) {
708 GetOrCreateStatsProcess(pid)->set_smr_pss_anon_kb(counter);
709 cached.smr_pss_anon_kb = counter;
710 }
711 } else if (strcmp(key.data(), "Pss_File") == 0) {
712 auto counter = ToUInt32(value.data());
713 if (counter != cached.smr_pss_file_kb) {
714 GetOrCreateStatsProcess(pid)->set_smr_pss_file_kb(counter);
715 cached.smr_pss_file_kb = counter;
716 }
717 } else if (strcmp(key.data(), "Pss_Shmem") == 0) {
718 auto counter = ToUInt32(value.data());
719 if (counter != cached.smr_pss_shmem_kb) {
720 GetOrCreateStatsProcess(pid)->set_smr_pss_shmem_kb(counter);
721 cached.smr_pss_shmem_kb = counter;
722 }
723 } else if (strcmp(key.data(), "SwapPss") == 0) {
724 auto counter = ToUInt32(value.data());
725 if (counter != cached.smr_swap_pss_kb) {
726 GetOrCreateStatsProcess(pid)->set_smr_swap_pss_kb(counter);
727 cached.smr_swap_pss_kb = counter;
728 }
729 }
730
731 key.clear();
732 state = kKey;
733 continue;
734 }
735
736 if (state == kKey) {
737 if (c == ':') {
738 state = kSeparator;
739 continue;
740 }
741 key.push_back(c);
742 continue;
743 }
744
745 if (state == kSeparator) {
746 if (isspace(c))
747 continue;
748 value.clear();
749 value.push_back(c);
750 state = kValue;
751 continue;
752 }
753
754 if (state == kValue) {
755 value.push_back(c);
756 }
757 }
758 return proc_status_has_mem_counters;
759 }
760
WriteFds(int32_t pid)761 void ProcessStatsDataSource::WriteFds(int32_t pid) {
762 if (!resolve_process_fds_) {
763 return;
764 }
765
766 base::StackString<256> path("%s/%" PRId32 "/fd", GetProcMountpoint(), pid);
767 base::ScopedDir proc_dir(opendir(path.c_str()));
768 if (!proc_dir) {
769 PERFETTO_DPLOG("Failed to opendir(%s)", path.c_str());
770 return;
771 }
772 while (struct dirent* dir_ent = readdir(*proc_dir)) {
773 if (dir_ent->d_type != DT_LNK)
774 continue;
775 auto fd = base::CStringToUInt64(dir_ent->d_name);
776 if (fd)
777 WriteSingleFd(pid, *fd);
778 }
779 }
780
WriteSingleFd(int32_t pid,uint64_t fd)781 void ProcessStatsDataSource::WriteSingleFd(int32_t pid, uint64_t fd) {
782 CachedProcessStats& cached = process_stats_cache_[pid];
783 if (cached.seen_fds.count(fd)) {
784 return;
785 }
786
787 base::StackString<128> proc_fd("%s/%" PRId32 "/fd/%" PRIu64,
788 GetProcMountpoint(), pid, fd);
789 std::array<char, 256> path;
790 ssize_t actual = readlink(proc_fd.c_str(), path.data(), path.size());
791 if (actual >= 0) {
792 auto* fd_info = GetOrCreateStatsProcess(pid)->add_fds();
793 fd_info->set_fd(fd);
794 fd_info->set_path(path.data(), static_cast<size_t>(actual));
795 cached.seen_fds.insert(fd);
796 } else if (ENOENT != errno) {
797 PERFETTO_DPLOG("Failed to readlink '%s'", proc_fd.c_str());
798 }
799 }
800
CacheProcFsScanStartTimestamp()801 uint64_t ProcessStatsDataSource::CacheProcFsScanStartTimestamp() {
802 if (!cur_procfs_scan_start_timestamp_)
803 cur_procfs_scan_start_timestamp_ =
804 static_cast<uint64_t>(base::GetBootTimeNs().count());
805 return cur_procfs_scan_start_timestamp_;
806 }
807
ClearIncrementalState()808 void ProcessStatsDataSource::ClearIncrementalState() {
809 PERFETTO_DLOG("ProcessStatsDataSource clearing incremental state.");
810 seen_pids_.clear();
811 skip_mem_for_pids_.clear();
812
813 cache_ticks_ = 0;
814 process_stats_cache_.clear();
815
816 // Set the relevant flag in the next packet.
817 did_clear_incremental_state_ = true;
818 }
819
820 } // namespace perfetto
821