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 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 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 60 inline int ToInt(const std::string& str) { 61 return atoi(str.c_str()); 62 } 63 64 } // namespace 65 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 78 base::WeakPtr<ProcessStatsDataSource> ProcessStatsDataSource::GetWeakPtr() 79 const { 80 return weak_factory_.GetWeakPtr(); 81 } 82 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 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 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 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 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 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 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 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 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 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