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