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