• 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 <utility>
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/hash.h"
30 #include "perfetto/ext/base/metatrace.h"
31 #include "perfetto/ext/base/scoped_file.h"
32 #include "perfetto/ext/base/string_splitter.h"
33 #include "perfetto/ext/base/string_utils.h"
34 #include "perfetto/tracing/core/data_source_config.h"
35 
36 #include "protos/perfetto/config/process_stats/process_stats_config.pbzero.h"
37 #include "protos/perfetto/trace/ps/process_stats.pbzero.h"
38 #include "protos/perfetto/trace/ps/process_tree.pbzero.h"
39 #include "protos/perfetto/trace/trace_packet.pbzero.h"
40 
41 // TODO(primiano): the code in this file assumes that PIDs are never recycled
42 // and that processes/threads never change names. Neither is always true.
43 
44 // The notion of PID in the Linux kernel is a bit confusing.
45 // - PID: is really the thread id (for the main thread: PID == TID).
46 // - TGID (thread group ID): is the Unix Process ID (the actual PID).
47 // - PID == TGID for the main thread: the TID of the main thread is also the PID
48 //   of the process.
49 // So, in this file, |pid| might refer to either a process id or a thread id.
50 
51 namespace perfetto {
52 
53 namespace {
54 
ReadNextNumericDir(DIR * dirp)55 int32_t ReadNextNumericDir(DIR* dirp) {
56   while (struct dirent* dir_ent = readdir(dirp)) {
57     if (dir_ent->d_type != DT_DIR)
58       continue;
59     auto int_value = base::CStringToInt32(dir_ent->d_name);
60     if (int_value)
61       return *int_value;
62   }
63   return 0;
64 }
65 
ToInt(const std::string & str)66 inline int ToInt(const std::string& str) {
67   return atoi(str.c_str());
68 }
69 
ToU32(const char * str)70 inline uint32_t ToU32(const char* str) {
71   return static_cast<uint32_t>(strtol(str, nullptr, 10));
72 }
73 
74 }  // namespace
75 
76 // static
77 const ProbesDataSource::Descriptor ProcessStatsDataSource::descriptor = {
78     /*name*/ "linux.process_stats",
79     /*flags*/ Descriptor::kHandlesIncrementalState,
80     /*fill_descriptor_func*/ nullptr,
81 };
82 
ProcessStatsDataSource(base::TaskRunner * task_runner,TracingSessionID session_id,std::unique_ptr<TraceWriter> writer,const DataSourceConfig & ds_config,std::unique_ptr<CpuFreqInfo> cpu_freq_info)83 ProcessStatsDataSource::ProcessStatsDataSource(
84     base::TaskRunner* task_runner,
85     TracingSessionID session_id,
86     std::unique_ptr<TraceWriter> writer,
87     const DataSourceConfig& ds_config,
88     std::unique_ptr<CpuFreqInfo> cpu_freq_info)
89     : ProbesDataSource(session_id, &descriptor),
90       task_runner_(task_runner),
91       writer_(std::move(writer)),
92       cpu_freq_info_(std::move(cpu_freq_info)),
93       weak_factory_(this) {
94   using protos::pbzero::ProcessStatsConfig;
95   ProcessStatsConfig::Decoder cfg(ds_config.process_stats_config_raw());
96   record_thread_names_ = cfg.record_thread_names();
97   dump_all_procs_on_start_ = cfg.scan_all_processes_on_start();
98   resolve_process_fds_ = cfg.resolve_process_fds();
99   scan_smaps_rollup_ = cfg.scan_smaps_rollup();
100 
101   enable_on_demand_dumps_ = true;
102   for (auto quirk = cfg.quirks(); quirk; ++quirk) {
103     if (*quirk == ProcessStatsConfig::DISABLE_ON_DEMAND)
104       enable_on_demand_dumps_ = false;
105   }
106 
107   poll_period_ms_ = cfg.proc_stats_poll_ms();
108   if (poll_period_ms_ > 0 && poll_period_ms_ < 100) {
109     PERFETTO_ILOG("proc_stats_poll_ms %" PRIu32
110                   " is less than minimum of 100ms. Increasing to 100ms.",
111                   poll_period_ms_);
112     poll_period_ms_ = 100;
113   }
114 
115   if (poll_period_ms_ > 0) {
116     auto proc_stats_ttl_ms = cfg.proc_stats_cache_ttl_ms();
117     process_stats_cache_ttl_ticks_ =
118         std::max(proc_stats_ttl_ms / poll_period_ms_, 1u);
119   }
120 }
121 
122 ProcessStatsDataSource::~ProcessStatsDataSource() = default;
123 
Start()124 void ProcessStatsDataSource::Start() {
125   if (dump_all_procs_on_start_) {
126     WriteAllProcesses();
127   }
128 
129   if (poll_period_ms_) {
130     auto weak_this = GetWeakPtr();
131     task_runner_->PostTask(std::bind(&ProcessStatsDataSource::Tick, weak_this));
132   }
133 }
134 
GetWeakPtr() const135 base::WeakPtr<ProcessStatsDataSource> ProcessStatsDataSource::GetWeakPtr()
136     const {
137   return weak_factory_.GetWeakPtr();
138 }
139 
WriteAllProcesses()140 void ProcessStatsDataSource::WriteAllProcesses() {
141   PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_WRITE_ALL_PROCESSES);
142   PERFETTO_DCHECK(!cur_ps_tree_);
143 
144   CacheProcFsScanStartTimestamp();
145 
146   base::ScopedDir proc_dir = OpenProcDir();
147   if (!proc_dir)
148     return;
149   base::FlatSet<int32_t> pids;
150   while (int32_t pid = ReadNextNumericDir(*proc_dir)) {
151     WriteProcessOrThread(pid);
152     base::StackString<128> task_path("/proc/%d/task", pid);
153     base::ScopedDir task_dir(opendir(task_path.c_str()));
154     if (!task_dir)
155       continue;
156 
157     while (int32_t tid = ReadNextNumericDir(*task_dir)) {
158       if (tid == pid)
159         continue;
160       if (record_thread_names_) {
161         WriteProcessOrThread(tid);
162       } else {
163         // If we are not interested in thread names, there is no need to open
164         // a proc file for each thread. We can save time and directly write the
165         // thread record. Note that we still read proc_status for recording
166         // NSpid entries.
167         std::string proc_status = ReadProcPidFile(tid, "status");
168         WriteThread(tid, pid, /*optional_name=*/nullptr, proc_status);
169       }
170     }
171 
172     pids.insert(pid);
173   }
174   FinalizeCurPacket();
175 
176   // Also collect any fds open when starting up
177   for (const auto pid : pids) {
178     cur_ps_stats_process_ = nullptr;
179     WriteFds(pid);
180   }
181   FinalizeCurPacket();
182 }
183 
OnPids(const base::FlatSet<int32_t> & pids)184 void ProcessStatsDataSource::OnPids(const base::FlatSet<int32_t>& pids) {
185   if (!enable_on_demand_dumps_)
186     return;
187   WriteProcessTree(pids);
188 }
189 
WriteProcessTree(const base::FlatSet<int32_t> & pids)190 void ProcessStatsDataSource::WriteProcessTree(
191     const base::FlatSet<int32_t>& pids) {
192   PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_ON_PIDS);
193   PERFETTO_DCHECK(!cur_ps_tree_);
194   int pids_scanned = 0;
195   for (int32_t pid : pids) {
196     if (seen_pids_.count(pid) || pid == 0)
197       continue;
198     WriteProcessOrThread(pid);
199     pids_scanned++;
200   }
201   FinalizeCurPacket();
202   PERFETTO_METATRACE_COUNTER(TAG_PROC_POLLERS, PS_PIDS_SCANNED, pids_scanned);
203 }
204 
OnRenamePids(const base::FlatSet<int32_t> & pids)205 void ProcessStatsDataSource::OnRenamePids(const base::FlatSet<int32_t>& pids) {
206   PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_ON_RENAME_PIDS);
207   if (!enable_on_demand_dumps_)
208     return;
209   PERFETTO_DCHECK(!cur_ps_tree_);
210   for (int32_t pid : pids)
211     seen_pids_.erase(pid);
212 }
213 
OnFds(const base::FlatSet<std::pair<pid_t,uint64_t>> & fds)214 void ProcessStatsDataSource::OnFds(
215     const base::FlatSet<std::pair<pid_t, uint64_t>>& fds) {
216   if (!resolve_process_fds_)
217     return;
218 
219   pid_t last_pid = 0;
220   for (const auto& tid_fd : fds) {
221     const auto tid = tid_fd.first;
222     const auto fd = tid_fd.second;
223 
224     auto it = seen_pids_.find(tid);
225     if (it == seen_pids_.end()) {
226       // TID is not known yet, skip resolving the fd and let the
227       // periodic stats scanner resolve the fd together with its TID later
228       continue;
229     }
230     const auto pid = it->tgid;
231 
232     if (last_pid != pid) {
233       cur_ps_stats_process_ = nullptr;
234       last_pid = pid;
235     }
236     WriteSingleFd(pid, fd);
237   }
238   FinalizeCurPacket();
239 }
240 
Flush(FlushRequestID,std::function<void ()> callback)241 void ProcessStatsDataSource::Flush(FlushRequestID,
242                                    std::function<void()> callback) {
243   // We shouldn't get this in the middle of WriteAllProcesses() or OnPids().
244   PERFETTO_DCHECK(!cur_ps_tree_);
245   PERFETTO_DCHECK(!cur_ps_stats_);
246   PERFETTO_DCHECK(!cur_ps_stats_process_);
247   writer_->Flush(callback);
248 }
249 
WriteProcessOrThread(int32_t pid)250 void ProcessStatsDataSource::WriteProcessOrThread(int32_t pid) {
251   // In case we're called from outside WriteAllProcesses()
252   CacheProcFsScanStartTimestamp();
253 
254   std::string proc_status = ReadProcPidFile(pid, "status");
255   if (proc_status.empty())
256     return;
257   int tgid = ToInt(ReadProcStatusEntry(proc_status, "Tgid:"));
258   int tid = ToInt(ReadProcStatusEntry(proc_status, "Pid:"));
259   if (tgid <= 0 || tid <= 0)
260     return;
261 
262   if (!seen_pids_.count(tgid)) {
263     // We need to read the status file if |pid| is non-main thread.
264     const std::string& proc_status_tgid =
265         (tgid == tid ? proc_status : ReadProcPidFile(tgid, "status"));
266     WriteProcess(tgid, proc_status_tgid);
267   }
268   if (pid != tgid) {
269     PERFETTO_DCHECK(!seen_pids_.count(pid));
270     std::string thread_name;
271     if (record_thread_names_)
272       thread_name = ReadProcStatusEntry(proc_status, "Name:");
273     WriteThread(pid, tgid, thread_name.empty() ? nullptr : thread_name.c_str(),
274                 proc_status);
275   }
276 }
277 
ReadNamespacedTids(int32_t tid,const std::string & proc_status,TidArray & out)278 void ProcessStatsDataSource::ReadNamespacedTids(int32_t tid,
279                                                 const std::string& proc_status,
280                                                 TidArray& out) {
281   // If a process has entered a PID namespace, NSpid shows the mapping in
282   // the status file like: NSpid:  28971   2
283   // NStgid: 28971   2
284   // which denotes that the thread (or process) 28971 in the root PID namespace
285   // has PID = 2 in the child PID namespace. This information can be read from
286   // the NSpid entry in /proc/<tid>/status.
287   if (proc_status.empty())
288     return;
289   std::string nspid = ReadProcStatusEntry(proc_status, "NSpid:");
290   if (nspid.empty())
291     return;
292 
293   out.fill(0);  // Zero-initialize the array in case the caller doesn't.
294   auto it = out.begin();
295 
296   base::StringSplitter ss(std::move(nspid), '\t');
297   ss.Next();  // Skip the 1st element.
298   PERFETTO_DCHECK(base::CStringToInt32(ss.cur_token()) == tid);
299   while (ss.Next()) {
300     PERFETTO_CHECK(it < out.end());
301     auto maybe_int32 = base::CStringToInt32(ss.cur_token());
302     PERFETTO_DCHECK(maybe_int32.has_value());
303     *it++ = *maybe_int32;
304   }
305 }
306 
WriteProcess(int32_t pid,const std::string & proc_status)307 void ProcessStatsDataSource::WriteProcess(int32_t pid,
308                                           const std::string& proc_status) {
309   PERFETTO_DCHECK(ToInt(ReadProcStatusEntry(proc_status, "Tgid:")) == pid);
310   // Assert that |proc_status| is not for a non-main thread.
311   PERFETTO_DCHECK(ToInt(ReadProcStatusEntry(proc_status, "Pid:")) == pid);
312   auto* proc = GetOrCreatePsTree()->add_processes();
313   proc->set_pid(pid);
314   proc->set_ppid(ToInt(ReadProcStatusEntry(proc_status, "PPid:")));
315   // Uid will have multiple entries, only return first (real uid).
316   proc->set_uid(ToInt(ReadProcStatusEntry(proc_status, "Uid:")));
317 
318   // Optionally write namespace-local PIDs.
319   TidArray nspids = {};
320   ReadNamespacedTids(pid, proc_status, nspids);
321   for (auto nspid : nspids) {
322     if (nspid == 0)  // No more elements.
323       break;
324     proc->add_nspid(nspid);
325   }
326 
327   std::string cmdline = ReadProcPidFile(pid, "cmdline");
328   if (!cmdline.empty()) {
329     if (cmdline.back() != '\0') {
330       // Some kernels can miss the NUL terminator due to a bug. b/147438623.
331       cmdline.push_back('\0');
332     }
333     using base::StringSplitter;
334     for (StringSplitter ss(&cmdline[0], cmdline.size(), '\0'); ss.Next();)
335       proc->add_cmdline(ss.cur_token());
336   } else {
337     // Nothing in cmdline so use the thread name instead (which is == "comm").
338     proc->add_cmdline(ReadProcStatusEntry(proc_status, "Name:").c_str());
339   }
340   seen_pids_.insert({pid, pid});
341 }
342 
WriteThread(int32_t tid,int32_t tgid,const char * optional_name,const std::string & proc_status)343 void ProcessStatsDataSource::WriteThread(int32_t tid,
344                                          int32_t tgid,
345                                          const char* optional_name,
346                                          const std::string& proc_status) {
347   auto* thread = GetOrCreatePsTree()->add_threads();
348   thread->set_tid(tid);
349   thread->set_tgid(tgid);
350   if (optional_name)
351     thread->set_name(optional_name);
352 
353   // Optionally write namespace-local TIDs.
354   TidArray nstids = {};
355   ReadNamespacedTids(tid, proc_status, nstids);
356   for (auto nstid : nstids) {
357     if (nstid == 0)  // No more elements.
358       break;
359     thread->add_nstid(nstid);
360   }
361   seen_pids_.insert({tid, tgid});
362 }
363 
GetProcMountpoint()364 const char* ProcessStatsDataSource::GetProcMountpoint() {
365   static constexpr char kDefaultProcMountpoint[] = "/proc";
366   return kDefaultProcMountpoint;
367 }
368 
OpenProcDir()369 base::ScopedDir ProcessStatsDataSource::OpenProcDir() {
370   base::ScopedDir proc_dir(opendir(GetProcMountpoint()));
371   if (!proc_dir)
372     PERFETTO_PLOG("Failed to opendir(%s)", GetProcMountpoint());
373   return proc_dir;
374 }
375 
ReadProcPidFile(int32_t pid,const std::string & file)376 std::string ProcessStatsDataSource::ReadProcPidFile(int32_t pid,
377                                                     const std::string& file) {
378   base::StackString<128> path("/proc/%" PRId32 "/%s", pid, file.c_str());
379   std::string contents;
380   contents.reserve(4096);
381   if (!base::ReadFile(path.c_str(), &contents))
382     return "";
383   return contents;
384 }
385 
ReadProcStatusEntry(const std::string & buf,const char * key)386 std::string ProcessStatsDataSource::ReadProcStatusEntry(const std::string& buf,
387                                                         const char* key) {
388   auto begin = buf.find(key);
389   if (begin == std::string::npos)
390     return "";
391   begin = buf.find_first_not_of(" \t", begin + strlen(key));
392   if (begin == std::string::npos)
393     return "";
394   auto end = buf.find('\n', begin);
395   if (end == std::string::npos || end <= begin)
396     return "";
397   return buf.substr(begin, end - begin);
398 }
399 
StartNewPacketIfNeeded()400 void ProcessStatsDataSource::StartNewPacketIfNeeded() {
401   if (cur_packet_)
402     return;
403   cur_packet_ = writer_->NewTracePacket();
404   cur_packet_->set_timestamp(CacheProcFsScanStartTimestamp());
405 
406   if (did_clear_incremental_state_) {
407     cur_packet_->set_incremental_state_cleared(true);
408     did_clear_incremental_state_ = false;
409   }
410 }
411 
GetOrCreatePsTree()412 protos::pbzero::ProcessTree* ProcessStatsDataSource::GetOrCreatePsTree() {
413   StartNewPacketIfNeeded();
414   if (!cur_ps_tree_)
415     cur_ps_tree_ = cur_packet_->set_process_tree();
416   cur_ps_stats_ = nullptr;
417   cur_ps_stats_process_ = nullptr;
418   return cur_ps_tree_;
419 }
420 
GetOrCreateStats()421 protos::pbzero::ProcessStats* ProcessStatsDataSource::GetOrCreateStats() {
422   StartNewPacketIfNeeded();
423   if (!cur_ps_stats_)
424     cur_ps_stats_ = cur_packet_->set_process_stats();
425   cur_ps_tree_ = nullptr;
426   cur_ps_stats_process_ = nullptr;
427   return cur_ps_stats_;
428 }
429 
430 protos::pbzero::ProcessStats_Process*
GetOrCreateStatsProcess(int32_t pid)431 ProcessStatsDataSource::GetOrCreateStatsProcess(int32_t pid) {
432   if (cur_ps_stats_process_)
433     return cur_ps_stats_process_;
434   cur_ps_stats_process_ = GetOrCreateStats()->add_processes();
435   cur_ps_stats_process_->set_pid(pid);
436   return cur_ps_stats_process_;
437 }
438 
FinalizeCurPacket()439 void ProcessStatsDataSource::FinalizeCurPacket() {
440   PERFETTO_DCHECK(!cur_ps_tree_ || cur_packet_);
441   PERFETTO_DCHECK(!cur_ps_stats_ || cur_packet_);
442   uint64_t now = static_cast<uint64_t>(base::GetBootTimeNs().count());
443   if (cur_ps_tree_) {
444     cur_ps_tree_->set_collection_end_timestamp(now);
445     cur_ps_tree_ = nullptr;
446   }
447   if (cur_ps_stats_) {
448     cur_ps_stats_->set_collection_end_timestamp(now);
449     cur_ps_stats_ = nullptr;
450   }
451   cur_ps_stats_process_ = nullptr;
452   cur_procfs_scan_start_timestamp_ = 0;
453   cur_packet_ = TraceWriter::TracePacketHandle{};
454 }
455 
456 // static
Tick(base::WeakPtr<ProcessStatsDataSource> weak_this)457 void ProcessStatsDataSource::Tick(
458     base::WeakPtr<ProcessStatsDataSource> weak_this) {
459   if (!weak_this)
460     return;
461   ProcessStatsDataSource& thiz = *weak_this;
462   uint32_t period_ms = thiz.poll_period_ms_;
463   uint32_t delay_ms =
464       period_ms -
465       static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
466   thiz.task_runner_->PostDelayedTask(
467       std::bind(&ProcessStatsDataSource::Tick, weak_this), delay_ms);
468   thiz.WriteAllProcessStats();
469 
470   // We clear the cache every process_stats_cache_ttl_ticks_ ticks.
471   if (++thiz.cache_ticks_ == thiz.process_stats_cache_ttl_ticks_) {
472     thiz.cache_ticks_ = 0;
473     thiz.process_stats_cache_.clear();
474   }
475 }
476 
WriteAllProcessStats()477 void ProcessStatsDataSource::WriteAllProcessStats() {
478   // TODO(primiano): implement filtering of processes by names.
479   // TODO(primiano): Have a pid cache to avoid wasting cycles reading kthreads
480   // proc files over and over. Same for non-filtered processes (see above).
481 
482   CacheProcFsScanStartTimestamp();
483   PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_WRITE_ALL_PROCESS_STATS);
484   base::ScopedDir proc_dir = OpenProcDir();
485   if (!proc_dir)
486     return;
487   base::FlatSet<int32_t> pids;
488   while (int32_t pid = ReadNextNumericDir(*proc_dir)) {
489     cur_ps_stats_process_ = nullptr;
490 
491     uint32_t pid_u = static_cast<uint32_t>(pid);
492     if (skip_stats_for_pids_.size() > pid_u && skip_stats_for_pids_[pid_u])
493       continue;
494 
495     std::string proc_status = ReadProcPidFile(pid, "status");
496     if (proc_status.empty())
497       continue;
498 
499     if (scan_smaps_rollup_) {
500       std::string proc_smaps_rollup = ReadProcPidFile(pid, "smaps_rollup");
501       proc_status.append(proc_smaps_rollup);
502     }
503 
504     if (!WriteMemCounters(pid, proc_status)) {
505       // If WriteMemCounters() fails the pid is very likely a kernel thread
506       // that has a valid /proc/[pid]/status but no memory values. In this
507       // case avoid keep polling it over and over.
508       if (skip_stats_for_pids_.size() <= pid_u)
509         skip_stats_for_pids_.resize(pid_u + 1);
510       skip_stats_for_pids_[pid_u] = true;
511       continue;
512     }
513 
514     std::string oom_score_adj = ReadProcPidFile(pid, "oom_score_adj");
515     if (!oom_score_adj.empty()) {
516       CachedProcessStats& cached = process_stats_cache_[pid];
517       auto counter = ToInt(oom_score_adj);
518       if (counter != cached.oom_score_adj) {
519         GetOrCreateStatsProcess(pid)->set_oom_score_adj(counter);
520         cached.oom_score_adj = counter;
521       }
522     }
523 
524     // Ensure we write data on any fds not seen before
525     WriteFds(pid);
526 
527     pids.insert(pid);
528   }
529   FinalizeCurPacket();
530 
531   // Ensure that we write once long-term process info (e.g., name) for new pids
532   // that we haven't seen before.
533   WriteProcessTree(pids);
534 }
535 
536 // Returns true if the stats for the given |pid| have been written, false it
537 // it failed (e.g., |pid| was a kernel thread and, as such, didn't report any
538 // memory counters).
WriteMemCounters(int32_t pid,const std::string & proc_status)539 bool ProcessStatsDataSource::WriteMemCounters(int32_t pid,
540                                               const std::string& proc_status) {
541   bool proc_status_has_mem_counters = false;
542   CachedProcessStats& cached = process_stats_cache_[pid];
543 
544   // Parse /proc/[pid]/status, which looks like this:
545   // Name:   cat
546   // Umask:  0027
547   // State:  R (running)
548   // FDSize: 256
549   // Groups: 4 20 24 46 997
550   // VmPeak:     5992 kB
551   // VmSize:     5992 kB
552   // VmLck:         0 kB
553   // ...
554   std::vector<char> key;
555   std::vector<char> value;
556   enum { kKey, kSeparator, kValue } state = kKey;
557   for (char c : proc_status) {
558     if (c == '\n') {
559       key.push_back('\0');
560       value.push_back('\0');
561 
562       // |value| will contain "1234 KB". We rely on strtol() (in ToU32()) to
563       // stop parsing at the first non-numeric character.
564       if (strcmp(key.data(), "VmSize") == 0) {
565         // Assume that if we see VmSize we'll see also the others.
566         proc_status_has_mem_counters = true;
567 
568         auto counter = ToU32(value.data());
569         if (counter != cached.vm_size_kb) {
570           GetOrCreateStatsProcess(pid)->set_vm_size_kb(counter);
571           cached.vm_size_kb = counter;
572         }
573       } else if (strcmp(key.data(), "VmLck") == 0) {
574         auto counter = ToU32(value.data());
575         if (counter != cached.vm_locked_kb) {
576           GetOrCreateStatsProcess(pid)->set_vm_locked_kb(counter);
577           cached.vm_locked_kb = counter;
578         }
579       } else if (strcmp(key.data(), "VmHWM") == 0) {
580         auto counter = ToU32(value.data());
581         if (counter != cached.vm_hvm_kb) {
582           GetOrCreateStatsProcess(pid)->set_vm_hwm_kb(counter);
583           cached.vm_hvm_kb = counter;
584         }
585       } else if (strcmp(key.data(), "VmRSS") == 0) {
586         auto counter = ToU32(value.data());
587         if (counter != cached.vm_rss_kb) {
588           GetOrCreateStatsProcess(pid)->set_vm_rss_kb(counter);
589           cached.vm_rss_kb = counter;
590         }
591       } else if (strcmp(key.data(), "RssAnon") == 0) {
592         auto counter = ToU32(value.data());
593         if (counter != cached.rss_anon_kb) {
594           GetOrCreateStatsProcess(pid)->set_rss_anon_kb(counter);
595           cached.rss_anon_kb = counter;
596         }
597       } else if (strcmp(key.data(), "RssFile") == 0) {
598         auto counter = ToU32(value.data());
599         if (counter != cached.rss_file_kb) {
600           GetOrCreateStatsProcess(pid)->set_rss_file_kb(counter);
601           cached.rss_file_kb = counter;
602         }
603       } else if (strcmp(key.data(), "RssShmem") == 0) {
604         auto counter = ToU32(value.data());
605         if (counter != cached.rss_shmem_kb) {
606           GetOrCreateStatsProcess(pid)->set_rss_shmem_kb(counter);
607           cached.rss_shmem_kb = counter;
608         }
609       } else if (strcmp(key.data(), "VmSwap") == 0) {
610         auto counter = ToU32(value.data());
611         if (counter != cached.vm_swap_kb) {
612           GetOrCreateStatsProcess(pid)->set_vm_swap_kb(counter);
613           cached.vm_swap_kb = counter;
614         }
615       // The entries below come from smaps_rollup, WriteAllProcessStats merges
616       // everything into the same buffer for convenience.
617       } else if (strcmp(key.data(), "Rss") == 0) {
618          auto counter = ToU32(value.data());
619         if (counter != cached.smr_rss_kb) {
620           GetOrCreateStatsProcess(pid)->set_smr_rss_kb(counter);
621           cached.smr_rss_kb = counter;
622         }
623       } else if (strcmp(key.data(), "Pss") == 0) {
624          auto counter = ToU32(value.data());
625         if (counter != cached.smr_pss_kb) {
626           GetOrCreateStatsProcess(pid)->set_smr_pss_kb(counter);
627           cached.smr_pss_kb = counter;
628         }
629       } else if (strcmp(key.data(), "Pss_Anon") == 0) {
630          auto counter = ToU32(value.data());
631         if (counter != cached.smr_pss_anon_kb) {
632           GetOrCreateStatsProcess(pid)->set_smr_pss_anon_kb(counter);
633           cached.smr_pss_anon_kb = counter;
634         }
635       } else if (strcmp(key.data(), "Pss_File") == 0) {
636          auto counter = ToU32(value.data());
637         if (counter != cached.smr_pss_file_kb) {
638           GetOrCreateStatsProcess(pid)->set_smr_pss_file_kb(counter);
639           cached.smr_pss_file_kb = counter;
640         }
641       } else if (strcmp(key.data(), "Pss_Shmem") == 0) {
642          auto counter = ToU32(value.data());
643         if (counter != cached.smr_pss_shmem_kb) {
644           GetOrCreateStatsProcess(pid)->set_smr_pss_shmem_kb(counter);
645           cached.smr_pss_shmem_kb = counter;
646         }
647       }
648 
649       key.clear();
650       state = kKey;
651       continue;
652     }
653 
654     if (state == kKey) {
655       if (c == ':') {
656         state = kSeparator;
657         continue;
658       }
659       key.push_back(c);
660       continue;
661     }
662 
663     if (state == kSeparator) {
664       if (isspace(c))
665         continue;
666       value.clear();
667       value.push_back(c);
668       state = kValue;
669       continue;
670     }
671 
672     if (state == kValue) {
673       value.push_back(c);
674     }
675   }
676   return proc_status_has_mem_counters;
677 }
678 
WriteFds(int32_t pid)679 void ProcessStatsDataSource::WriteFds(int32_t pid) {
680   if (!resolve_process_fds_) {
681     return;
682   }
683 
684   base::StackString<256> path("%s/%" PRId32 "/fd", GetProcMountpoint(), pid);
685   base::ScopedDir proc_dir(opendir(path.c_str()));
686   if (!proc_dir) {
687     PERFETTO_DPLOG("Failed to opendir(%s)", path.c_str());
688     return;
689   }
690   while (struct dirent* dir_ent = readdir(*proc_dir)) {
691     if (dir_ent->d_type != DT_LNK)
692       continue;
693     auto fd = base::CStringToUInt64(dir_ent->d_name);
694     if (fd)
695       WriteSingleFd(pid, *fd);
696   }
697 }
698 
WriteSingleFd(int32_t pid,uint64_t fd)699 void ProcessStatsDataSource::WriteSingleFd(int32_t pid, uint64_t fd) {
700   CachedProcessStats& cached = process_stats_cache_[pid];
701   if (cached.seen_fds.count(fd)) {
702     return;
703   }
704 
705   base::StackString<128> proc_fd("%s/%" PRId32 "/fd/%" PRIu64,
706                                  GetProcMountpoint(), pid, fd);
707   std::array<char, 256> path;
708   ssize_t actual = readlink(proc_fd.c_str(), path.data(), path.size());
709   if (actual >= 0) {
710     auto* fd_info = GetOrCreateStatsProcess(pid)->add_fds();
711     fd_info->set_fd(fd);
712     fd_info->set_path(path.data(), static_cast<size_t>(actual));
713     cached.seen_fds.insert(fd);
714   } else if (ENOENT != errno) {
715     PERFETTO_DPLOG("Failed to readlink '%s'", proc_fd.c_str());
716   }
717 }
718 
CacheProcFsScanStartTimestamp()719 uint64_t ProcessStatsDataSource::CacheProcFsScanStartTimestamp() {
720   if (!cur_procfs_scan_start_timestamp_)
721     cur_procfs_scan_start_timestamp_ =
722         static_cast<uint64_t>(base::GetBootTimeNs().count());
723   return cur_procfs_scan_start_timestamp_;
724 }
725 
ClearIncrementalState()726 void ProcessStatsDataSource::ClearIncrementalState() {
727   PERFETTO_DLOG("ProcessStatsDataSource clearing incremental state.");
728   seen_pids_.clear();
729   skip_stats_for_pids_.clear();
730 
731   cache_ticks_ = 0;
732   process_stats_cache_.clear();
733 
734   // Set the relevant flag in the next packet.
735   did_clear_incremental_state_ = true;
736 }
737 
738 }  // namespace perfetto
739