Home | History | Annotate | Download | only in probes
      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