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