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