• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     // This comes up at least for zombies and kthreads.
389     proc->add_cmdline(ProcStatusEntry(proc_status, "Name:"));
390     proc->set_cmdline_is_comm(true);
391   }
392 
393   if (record_process_age_ && !proc_stat.empty()) {
394     std::optional<ProcessRuntimes> times = ParseProcessRuntimes(proc_stat);
395     if (times.has_value()) {
396       proc->set_process_start_from_boot(times->starttime);
397     }
398   }
399 
400   // Linux v6.4 and onwards has an explicit field for whether this is a kthread.
401   std::optional<int32_t> kthread =
402       base::StringToInt32(ProcStatusEntry(proc_status, "Kthread:"));
403   if (kthread.has_value() && (*kthread == 0 || *kthread == 1)) {
404     proc->set_is_kthread(*kthread);
405   }
406 
407   seen_pids_.insert({pid, pid});
408   return namespaced;
409 }
410 
WriteThread(int32_t tid,int32_t tgid)411 void ProcessStatsDataSource::WriteThread(int32_t tid, int32_t tgid) {
412   auto* thread = GetOrCreatePsTree()->add_threads();
413   thread->set_tid(tid);
414   thread->set_tgid(tgid);
415   seen_pids_.insert({tid, tgid});
416 }
417 
418 // Emit thread proto that requires /proc/tid/status contents. May also be called
419 // from places where the proc status contents are already available, but might
420 // end up unused.
WriteDetailedThread(int32_t tid,int32_t tgid,const std::string & proc_status)421 void ProcessStatsDataSource::WriteDetailedThread(
422     int32_t tid,
423     int32_t tgid,
424     const std::string& proc_status) {
425   auto* thread = GetOrCreatePsTree()->add_threads();
426   thread->set_tid(tid);
427   thread->set_tgid(tgid);
428 
429   ParseNamespacedTids(proc_status,
430                       [thread](int32_t nstid) { thread->add_nstid(nstid); });
431 
432   if (record_thread_names_) {
433     std::string thread_name = ProcStatusEntry(proc_status, "Name:");
434     thread->set_name(thread_name);
435   }
436   seen_pids_.insert({tid, tgid});
437 }
438 
GetProcMountpoint()439 const char* ProcessStatsDataSource::GetProcMountpoint() {
440   static constexpr char kDefaultProcMountpoint[] = "/proc";
441   return kDefaultProcMountpoint;
442 }
443 
OpenProcDir()444 base::ScopedDir ProcessStatsDataSource::OpenProcDir() {
445   base::ScopedDir proc_dir(opendir(GetProcMountpoint()));
446   if (!proc_dir)
447     PERFETTO_PLOG("Failed to opendir(%s)", GetProcMountpoint());
448   return proc_dir;
449 }
450 
ReadProcPidFile(int32_t pid,const std::string & file)451 std::string ProcessStatsDataSource::ReadProcPidFile(int32_t pid,
452                                                     const std::string& file) {
453   base::StackString<128> path("/proc/%" PRId32 "/%s", pid, file.c_str());
454   std::string contents;
455   contents.reserve(4096);
456   if (!base::ReadFile(path.c_str(), &contents))
457     return "";
458   return contents;
459 }
460 
StartNewPacketIfNeeded()461 void ProcessStatsDataSource::StartNewPacketIfNeeded() {
462   if (cur_packet_)
463     return;
464   cur_packet_ = writer_->NewTracePacket();
465   cur_packet_->set_timestamp(CacheProcFsScanStartTimestamp());
466 
467   if (did_clear_incremental_state_) {
468     cur_packet_->set_incremental_state_cleared(true);
469     did_clear_incremental_state_ = false;
470   }
471 }
472 
GetOrCreatePsTree()473 protos::pbzero::ProcessTree* ProcessStatsDataSource::GetOrCreatePsTree() {
474   StartNewPacketIfNeeded();
475   if (!cur_ps_tree_)
476     cur_ps_tree_ = cur_packet_->set_process_tree();
477   cur_ps_stats_ = nullptr;
478   cur_ps_stats_process_ = nullptr;
479   return cur_ps_tree_;
480 }
481 
GetOrCreateStats()482 protos::pbzero::ProcessStats* ProcessStatsDataSource::GetOrCreateStats() {
483   StartNewPacketIfNeeded();
484   if (!cur_ps_stats_)
485     cur_ps_stats_ = cur_packet_->set_process_stats();
486   cur_ps_tree_ = nullptr;
487   cur_ps_stats_process_ = nullptr;
488   return cur_ps_stats_;
489 }
490 
491 protos::pbzero::ProcessStats_Process*
GetOrCreateStatsProcess(int32_t pid)492 ProcessStatsDataSource::GetOrCreateStatsProcess(int32_t pid) {
493   if (cur_ps_stats_process_)
494     return cur_ps_stats_process_;
495   cur_ps_stats_process_ = GetOrCreateStats()->add_processes();
496   cur_ps_stats_process_->set_pid(pid);
497   return cur_ps_stats_process_;
498 }
499 
FinalizeCurPacket()500 void ProcessStatsDataSource::FinalizeCurPacket() {
501   PERFETTO_DCHECK(!cur_ps_tree_ || cur_packet_);
502   PERFETTO_DCHECK(!cur_ps_stats_ || cur_packet_);
503   uint64_t now = static_cast<uint64_t>(base::GetBootTimeNs().count());
504   if (cur_ps_tree_) {
505     cur_ps_tree_->set_collection_end_timestamp(now);
506     cur_ps_tree_ = nullptr;
507   }
508   if (cur_ps_stats_) {
509     cur_ps_stats_->set_collection_end_timestamp(now);
510     cur_ps_stats_ = nullptr;
511   }
512   cur_ps_stats_process_ = nullptr;
513   cur_procfs_scan_start_timestamp_ = 0;
514   cur_packet_ = TraceWriter::TracePacketHandle{};
515 }
516 
517 // static
Tick(base::WeakPtr<ProcessStatsDataSource> weak_this)518 void ProcessStatsDataSource::Tick(
519     base::WeakPtr<ProcessStatsDataSource> weak_this) {
520   if (!weak_this)
521     return;
522   ProcessStatsDataSource& thiz = *weak_this;
523   uint32_t period_ms = thiz.poll_period_ms_;
524   uint32_t delay_ms =
525       period_ms -
526       static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
527   thiz.task_runner_->PostDelayedTask(
528       std::bind(&ProcessStatsDataSource::Tick, weak_this), delay_ms);
529   thiz.WriteAllProcessStats();
530 
531   // We clear the cache every process_stats_cache_ttl_ticks_ ticks.
532   if (++thiz.cache_ticks_ == thiz.process_stats_cache_ttl_ticks_) {
533     thiz.cache_ticks_ = 0;
534     thiz.process_stats_cache_.clear();
535   }
536 }
537 
WriteAllProcessStats()538 void ProcessStatsDataSource::WriteAllProcessStats() {
539   CacheProcFsScanStartTimestamp();
540   PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_WRITE_ALL_PROCESS_STATS);
541   base::ScopedDir proc_dir = OpenProcDir();
542   if (!proc_dir)
543     return;
544   base::FlatSet<int32_t> pids;
545   while (int32_t pid = ReadNextNumericDir(*proc_dir)) {
546     cur_ps_stats_process_ = nullptr;
547     uint32_t pid_u = static_cast<uint32_t>(pid);
548 
549     // optional /proc/pid/stat fields
550     if (record_process_runtime_) {
551       std::string proc_stat = ReadProcPidFile(pid, "stat");
552       if (WriteProcessRuntimes(pid, proc_stat)) {
553         pids.insert(pid);
554       }
555     }
556 
557     // memory counters
558     if (skip_mem_for_pids_.size() > pid_u && skip_mem_for_pids_[pid_u])
559       continue;
560 
561     std::string proc_status = ReadProcPidFile(pid, "status");
562     if (proc_status.empty())
563       continue;
564 
565     if (scan_smaps_rollup_) {
566       std::string proc_smaps_rollup = ReadProcPidFile(pid, "smaps_rollup");
567       proc_status.append(proc_smaps_rollup);
568     }
569 
570     if (!WriteMemCounters(pid, proc_status)) {
571       // If WriteMemCounters() fails the pid is very likely a kernel thread
572       // that has a valid /proc/[pid]/status but no memory values. In this
573       // case avoid keep polling it over and over.
574       if (skip_mem_for_pids_.size() <= pid_u)
575         skip_mem_for_pids_.resize(pid_u + 1);
576       skip_mem_for_pids_[pid_u] = true;
577       continue;
578     }
579 
580     std::string oom_score_adj = ReadProcPidFile(pid, "oom_score_adj");
581     if (!oom_score_adj.empty()) {
582       CachedProcessStats& cached = process_stats_cache_[pid];
583       int32_t counter = ToInt32(oom_score_adj);
584       if (counter != cached.oom_score_adj) {
585         GetOrCreateStatsProcess(pid)->set_oom_score_adj(counter);
586         cached.oom_score_adj = counter;
587       }
588     }
589 
590     // Ensure we write data on any fds not seen before (niche option).
591     WriteFds(pid);
592 
593     pids.insert(pid);
594   }
595   FinalizeCurPacket();
596 
597   // Ensure that we write once long-term process info (e.g., name) for new pids
598   // that we haven't seen before.
599   WriteProcessTree(pids);
600 }
601 
WriteProcessRuntimes(int32_t pid,const std::string & proc_stat)602 bool ProcessStatsDataSource::WriteProcessRuntimes(
603     int32_t pid,
604     const std::string& proc_stat) {
605   std::optional<ProcessRuntimes> times = ParseProcessRuntimes(proc_stat);
606   if (!times.has_value())
607     return false;
608 
609   CachedProcessStats& cached = process_stats_cache_[pid];
610   if (times->utime != cached.runtime_user_mode_ns) {
611     GetOrCreateStatsProcess(pid)->set_runtime_user_mode(times->utime);
612     cached.runtime_user_mode_ns = times->utime;
613   }
614   if (times->stime != cached.runtime_kernel_mode_ns) {
615     GetOrCreateStatsProcess(pid)->set_runtime_kernel_mode(times->stime);
616     cached.runtime_kernel_mode_ns = times->stime;
617   }
618   return true;
619 }
620 
621 // Returns true if the stats for the given |pid| have been written, false it
622 // it failed (e.g., |pid| was a kernel thread and, as such, didn't report any
623 // memory counters).
WriteMemCounters(int32_t pid,const std::string & proc_status)624 bool ProcessStatsDataSource::WriteMemCounters(int32_t pid,
625                                               const std::string& proc_status) {
626   bool proc_status_has_mem_counters = false;
627   CachedProcessStats& cached = process_stats_cache_[pid];
628 
629   // Parse /proc/[pid]/status, which looks like this:
630   // Name:   cat
631   // Umask:  0027
632   // State:  R (running)
633   // FDSize: 256
634   // Groups: 4 20 24 46 997
635   // VmPeak:     5992 kB
636   // VmSize:     5992 kB
637   // VmLck:         0 kB
638   // ...
639   std::vector<char> key;
640   std::vector<char> value;
641   enum { kKey, kSeparator, kValue } state = kKey;
642   for (char c : proc_status) {
643     if (c == '\n') {
644       key.push_back('\0');
645       value.push_back('\0');
646 
647       // |value| will contain "1234 KB". We rely on ToUInt32() to stop parsing
648       // at the first non-numeric character.
649       if (strcmp(key.data(), "VmSize") == 0) {
650         // Assume that if we see VmSize we'll see also the others.
651         proc_status_has_mem_counters = true;
652 
653         auto counter = ToUInt32(value.data());
654         if (counter != cached.vm_size_kb) {
655           GetOrCreateStatsProcess(pid)->set_vm_size_kb(counter);
656           cached.vm_size_kb = counter;
657         }
658       } else if (strcmp(key.data(), "VmLck") == 0) {
659         auto counter = ToUInt32(value.data());
660         if (counter != cached.vm_locked_kb) {
661           GetOrCreateStatsProcess(pid)->set_vm_locked_kb(counter);
662           cached.vm_locked_kb = counter;
663         }
664       } else if (strcmp(key.data(), "VmHWM") == 0) {
665         auto counter = ToUInt32(value.data());
666         if (counter != cached.vm_hvm_kb) {
667           GetOrCreateStatsProcess(pid)->set_vm_hwm_kb(counter);
668           cached.vm_hvm_kb = counter;
669         }
670       } else if (strcmp(key.data(), "VmRSS") == 0) {
671         auto counter = ToUInt32(value.data());
672         if (counter != cached.vm_rss_kb) {
673           GetOrCreateStatsProcess(pid)->set_vm_rss_kb(counter);
674           cached.vm_rss_kb = counter;
675         }
676       } else if (strcmp(key.data(), "RssAnon") == 0) {
677         auto counter = ToUInt32(value.data());
678         if (counter != cached.rss_anon_kb) {
679           GetOrCreateStatsProcess(pid)->set_rss_anon_kb(counter);
680           cached.rss_anon_kb = counter;
681         }
682       } else if (strcmp(key.data(), "RssFile") == 0) {
683         auto counter = ToUInt32(value.data());
684         if (counter != cached.rss_file_kb) {
685           GetOrCreateStatsProcess(pid)->set_rss_file_kb(counter);
686           cached.rss_file_kb = counter;
687         }
688       } else if (strcmp(key.data(), "RssShmem") == 0) {
689         auto counter = ToUInt32(value.data());
690         if (counter != cached.rss_shmem_kb) {
691           GetOrCreateStatsProcess(pid)->set_rss_shmem_kb(counter);
692           cached.rss_shmem_kb = counter;
693         }
694       } else if (strcmp(key.data(), "VmSwap") == 0) {
695         auto counter = ToUInt32(value.data());
696         if (counter != cached.vm_swap_kb) {
697           GetOrCreateStatsProcess(pid)->set_vm_swap_kb(counter);
698           cached.vm_swap_kb = counter;
699         }
700         // The entries below come from smaps_rollup, WriteAllProcessStats merges
701         // everything into the same buffer for convenience.
702       } else if (strcmp(key.data(), "Rss") == 0) {
703         auto counter = ToUInt32(value.data());
704         if (counter != cached.smr_rss_kb) {
705           GetOrCreateStatsProcess(pid)->set_smr_rss_kb(counter);
706           cached.smr_rss_kb = counter;
707         }
708       } else if (strcmp(key.data(), "Pss") == 0) {
709         auto counter = ToUInt32(value.data());
710         if (counter != cached.smr_pss_kb) {
711           GetOrCreateStatsProcess(pid)->set_smr_pss_kb(counter);
712           cached.smr_pss_kb = counter;
713         }
714       } else if (strcmp(key.data(), "Pss_Anon") == 0) {
715         auto counter = ToUInt32(value.data());
716         if (counter != cached.smr_pss_anon_kb) {
717           GetOrCreateStatsProcess(pid)->set_smr_pss_anon_kb(counter);
718           cached.smr_pss_anon_kb = counter;
719         }
720       } else if (strcmp(key.data(), "Pss_File") == 0) {
721         auto counter = ToUInt32(value.data());
722         if (counter != cached.smr_pss_file_kb) {
723           GetOrCreateStatsProcess(pid)->set_smr_pss_file_kb(counter);
724           cached.smr_pss_file_kb = counter;
725         }
726       } else if (strcmp(key.data(), "Pss_Shmem") == 0) {
727         auto counter = ToUInt32(value.data());
728         if (counter != cached.smr_pss_shmem_kb) {
729           GetOrCreateStatsProcess(pid)->set_smr_pss_shmem_kb(counter);
730           cached.smr_pss_shmem_kb = counter;
731         }
732       } else if (strcmp(key.data(), "SwapPss") == 0) {
733         auto counter = ToUInt32(value.data());
734         if (counter != cached.smr_swap_pss_kb) {
735           GetOrCreateStatsProcess(pid)->set_smr_swap_pss_kb(counter);
736           cached.smr_swap_pss_kb = counter;
737         }
738       }
739 
740       key.clear();
741       state = kKey;
742       continue;
743     }
744 
745     if (state == kKey) {
746       if (c == ':') {
747         state = kSeparator;
748         continue;
749       }
750       key.push_back(c);
751       continue;
752     }
753 
754     if (state == kSeparator) {
755       if (isspace(c))
756         continue;
757       value.clear();
758       value.push_back(c);
759       state = kValue;
760       continue;
761     }
762 
763     if (state == kValue) {
764       value.push_back(c);
765     }
766   }
767   return proc_status_has_mem_counters;
768 }
769 
WriteFds(int32_t pid)770 void ProcessStatsDataSource::WriteFds(int32_t pid) {
771   if (!resolve_process_fds_) {
772     return;
773   }
774 
775   base::StackString<256> path("%s/%" PRId32 "/fd", GetProcMountpoint(), pid);
776   base::ScopedDir proc_dir(opendir(path.c_str()));
777   if (!proc_dir) {
778     PERFETTO_DPLOG("Failed to opendir(%s)", path.c_str());
779     return;
780   }
781   while (struct dirent* dir_ent = readdir(*proc_dir)) {
782     if (dir_ent->d_type != DT_LNK)
783       continue;
784     auto fd = base::CStringToUInt64(dir_ent->d_name);
785     if (fd)
786       WriteSingleFd(pid, *fd);
787   }
788 }
789 
WriteSingleFd(int32_t pid,uint64_t fd)790 void ProcessStatsDataSource::WriteSingleFd(int32_t pid, uint64_t fd) {
791   CachedProcessStats& cached = process_stats_cache_[pid];
792   if (cached.seen_fds.count(fd)) {
793     return;
794   }
795 
796   base::StackString<128> proc_fd("%s/%" PRId32 "/fd/%" PRIu64,
797                                  GetProcMountpoint(), pid, fd);
798   std::array<char, 256> path;
799   ssize_t actual = readlink(proc_fd.c_str(), path.data(), path.size());
800   if (actual >= 0) {
801     auto* fd_info = GetOrCreateStatsProcess(pid)->add_fds();
802     fd_info->set_fd(fd);
803     fd_info->set_path(path.data(), static_cast<size_t>(actual));
804     cached.seen_fds.insert(fd);
805   } else if (ENOENT != errno) {
806     PERFETTO_DPLOG("Failed to readlink '%s'", proc_fd.c_str());
807   }
808 }
809 
CacheProcFsScanStartTimestamp()810 uint64_t ProcessStatsDataSource::CacheProcFsScanStartTimestamp() {
811   if (!cur_procfs_scan_start_timestamp_)
812     cur_procfs_scan_start_timestamp_ =
813         static_cast<uint64_t>(base::GetBootTimeNs().count());
814   return cur_procfs_scan_start_timestamp_;
815 }
816 
ClearIncrementalState()817 void ProcessStatsDataSource::ClearIncrementalState() {
818   PERFETTO_DLOG("ProcessStatsDataSource clearing incremental state.");
819   seen_pids_.clear();
820   skip_mem_for_pids_.clear();
821 
822   cache_ticks_ = 0;
823   process_stats_cache_.clear();
824 
825   // Set the relevant flag in the next packet.
826   did_clear_incremental_state_ = true;
827 }
828 
829 }  // namespace perfetto
830