• 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/process_stats_data_source.h"
18 
19 #include <stdlib.h>
20 
21 #include <utility>
22 
23 #include "perfetto/base/file_utils.h"
24 #include "perfetto/base/scoped_file.h"
25 #include "perfetto/base/string_splitter.h"
26 #include "perfetto/trace/trace_packet.pbzero.h"
27 
28 // TODO(primiano): the code in this file assumes that PIDs are never recycled
29 // and that processes/threads never change names. Neither is always true.
30 
31 // The notion of PID in the Linux kernel is a bit confusing.
32 // - PID: is really the thread id (for the main thread: PID == TID).
33 // - TGID (thread group ID): is the Unix Process ID (the actual PID).
34 // - PID == TGID for the main thread: the TID of the main thread is also the PID
35 //   of the process.
36 // So, in this file, |pid| might refer to either a process id or a thread id.
37 
38 namespace perfetto {
39 
40 namespace {
41 
IsNumeric(const char * str)42 bool IsNumeric(const char* str) {
43   if (!str || !*str)
44     return false;
45   for (const char* c = str; *c; c++) {
46     if (!isdigit(*c))
47       return false;
48   }
49   return true;
50 }
51 
ReadNextNumericDir(DIR * dirp)52 int32_t ReadNextNumericDir(DIR* dirp) {
53   while (struct dirent* dir_ent = readdir(dirp)) {
54     if (dir_ent->d_type == DT_DIR && IsNumeric(dir_ent->d_name))
55       return atoi(dir_ent->d_name);
56   }
57   return 0;
58 }
59 
ToInt(const std::string & str)60 inline int ToInt(const std::string& str) {
61   return atoi(str.c_str());
62 }
63 
64 }  // namespace
65 
ProcessStatsDataSource(TracingSessionID id,std::unique_ptr<TraceWriter> writer,const DataSourceConfig & config)66 ProcessStatsDataSource::ProcessStatsDataSource(
67     TracingSessionID id,
68     std::unique_ptr<TraceWriter> writer,
69     const DataSourceConfig& config)
70     : session_id_(id),
71       writer_(std::move(writer)),
72       config_(config),
73       record_thread_names_(config.process_stats_config().record_thread_names()),
74       weak_factory_(this) {}
75 
76 ProcessStatsDataSource::~ProcessStatsDataSource() = default;
77 
GetWeakPtr() const78 base::WeakPtr<ProcessStatsDataSource> ProcessStatsDataSource::GetWeakPtr()
79     const {
80   return weak_factory_.GetWeakPtr();
81 }
82 
WriteAllProcesses()83 void ProcessStatsDataSource::WriteAllProcesses() {
84   PERFETTO_DCHECK(!cur_ps_tree_);
85   base::ScopedDir proc_dir(opendir("/proc"));
86   if (!proc_dir) {
87     PERFETTO_PLOG("Failed to opendir(/proc)");
88     return;
89   }
90   while (int32_t pid = ReadNextNumericDir(*proc_dir)) {
91     WriteProcessOrThread(pid);
92     char task_path[255];
93     sprintf(task_path, "/proc/%d/task", pid);
94     base::ScopedDir task_dir(opendir(task_path));
95     if (!task_dir)
96       continue;
97     while (int32_t tid = ReadNextNumericDir(*task_dir)) {
98       if (tid == pid)
99         continue;
100       WriteProcessOrThread(tid);
101     }
102   }
103   FinalizeCurPsTree();
104 }
105 
OnPids(const std::vector<int32_t> & pids)106 void ProcessStatsDataSource::OnPids(const std::vector<int32_t>& pids) {
107   PERFETTO_DCHECK(!cur_ps_tree_);
108   for (int32_t pid : pids) {
109     if (seen_pids_.count(pid) || pid == 0)
110       continue;
111     WriteProcessOrThread(pid);
112   }
113   FinalizeCurPsTree();
114 }
115 
Flush()116 void ProcessStatsDataSource::Flush() {
117   // We shouldn't get this in the middle of WriteAllProcesses() or OnPids().
118   PERFETTO_DCHECK(!cur_ps_tree_);
119 
120   writer_->Flush();
121 }
122 
WriteProcessOrThread(int32_t pid)123 void ProcessStatsDataSource::WriteProcessOrThread(int32_t pid) {
124   std::string proc_status = ReadProcPidFile(pid, "status");
125   if (proc_status.empty())
126     return;
127   int tgid = ToInt(ReadProcStatusEntry(proc_status, "Tgid:"));
128   if (tgid <= 0)
129     return;
130   if (!seen_pids_.count(tgid))
131     WriteProcess(tgid, proc_status);
132   if (pid != tgid) {
133     PERFETTO_DCHECK(!seen_pids_.count(pid));
134     WriteThread(pid, tgid, proc_status);
135   }
136 }
137 
WriteProcess(int32_t pid,const std::string & proc_status)138 void ProcessStatsDataSource::WriteProcess(int32_t pid,
139                                           const std::string& proc_status) {
140   PERFETTO_DCHECK(ToInt(ReadProcStatusEntry(proc_status, "Tgid:")) == pid);
141   auto* proc = GetOrCreatePsTree()->add_processes();
142   proc->set_pid(pid);
143   proc->set_ppid(ToInt(ReadProcStatusEntry(proc_status, "PPid:")));
144 
145   std::string cmdline = ReadProcPidFile(pid, "cmdline");
146   if (!cmdline.empty()) {
147     using base::StringSplitter;
148     for (StringSplitter ss(&cmdline[0], cmdline.size(), '\0'); ss.Next();)
149       proc->add_cmdline(ss.cur_token());
150   } else {
151     // Nothing in cmdline so use the thread name instead (which is == "comm").
152     proc->add_cmdline(ReadProcStatusEntry(proc_status, "Name:").c_str());
153   }
154   seen_pids_.emplace(pid);
155 }
156 
WriteThread(int32_t tid,int32_t tgid,const std::string & proc_status)157 void ProcessStatsDataSource::WriteThread(int32_t tid,
158                                          int32_t tgid,
159                                          const std::string& proc_status) {
160   auto* thread = GetOrCreatePsTree()->add_threads();
161   thread->set_tid(tid);
162   thread->set_tgid(tgid);
163   if (record_thread_names_)
164     thread->set_name(ReadProcStatusEntry(proc_status, "Name:").c_str());
165   seen_pids_.emplace(tid);
166 }
167 
ReadProcPidFile(int32_t pid,const std::string & file)168 std::string ProcessStatsDataSource::ReadProcPidFile(int32_t pid,
169                                                     const std::string& file) {
170   std::string contents;
171   contents.reserve(4096);
172   if (!base::ReadFile("/proc/" + std::to_string(pid) + "/" + file, &contents))
173     return "";
174   return contents;
175 }
176 
ReadProcStatusEntry(const std::string & buf,const char * key)177 std::string ProcessStatsDataSource::ReadProcStatusEntry(const std::string& buf,
178                                                         const char* key) {
179   auto begin = buf.find(key);
180   if (begin == std::string::npos)
181     return "";
182   begin = buf.find_first_not_of(" \t", begin + strlen(key));
183   if (begin == std::string::npos)
184     return "";
185   auto end = buf.find('\n', begin);
186   if (end == std::string::npos || end <= begin)
187     return "";
188   return buf.substr(begin, end - begin);
189 }
190 
GetOrCreatePsTree()191 protos::pbzero::ProcessTree* ProcessStatsDataSource::GetOrCreatePsTree() {
192   if (!cur_ps_tree_) {
193     cur_packet_ = writer_->NewTracePacket();
194     cur_ps_tree_ = cur_packet_->set_process_tree();
195   }
196   return cur_ps_tree_;
197 }
198 
FinalizeCurPsTree()199 void ProcessStatsDataSource::FinalizeCurPsTree() {
200   if (!cur_ps_tree_) {
201     PERFETTO_DCHECK(!cur_packet_);
202     return;
203   }
204   cur_ps_tree_ = nullptr;
205   cur_packet_ = TraceWriter::TracePacketHandle{};
206 }
207 
208 }  // namespace perfetto
209