• 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/profiling/common/proc_utils.h"
18 
19 #include <inttypes.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 
23 #include "perfetto/ext/base/file_utils.h"
24 #include "perfetto/ext/base/optional.h"
25 #include "perfetto/profiling/normalize.h"
26 
27 namespace perfetto {
28 namespace profiling {
29 namespace {
30 
GetProcFile(pid_t pid,const char * file,char * filename_buf,size_t size)31 bool GetProcFile(pid_t pid, const char* file, char* filename_buf, size_t size) {
32   ssize_t written = snprintf(filename_buf, size, "/proc/%d/%s", pid, file);
33   if (written < 0 || static_cast<size_t>(written) >= size) {
34     if (written < 0)
35       PERFETTO_ELOG("Failed to concatenate cmdline file.");
36     else
37       PERFETTO_ELOG("Overflow when concatenating cmdline file.");
38     return false;
39   }
40   return true;
41 }
42 
ParseProcStatusSize(const std::string & status,const std::string & key)43 base::Optional<uint32_t> ParseProcStatusSize(const std::string& status,
44                                              const std::string& key) {
45   auto entry_idx = status.find(key);
46   if (entry_idx == std::string::npos)
47     return {};
48   entry_idx = status.find_first_not_of(" \t", entry_idx + key.size());
49   if (entry_idx == std::string::npos)
50     return {};
51   int32_t val = atoi(status.c_str() + entry_idx);
52   if (val < 0) {
53     PERFETTO_ELOG("Unexpected value reading %s", key.c_str());
54     return {};
55   }
56   return static_cast<uint32_t>(val);
57 }
58 }  // namespace
59 
NormalizeCmdlines(const std::vector<std::string> & cmdlines)60 base::Optional<std::vector<std::string>> NormalizeCmdlines(
61     const std::vector<std::string>& cmdlines) {
62   std::vector<std::string> normalized_cmdlines;
63   normalized_cmdlines.reserve(cmdlines.size());
64 
65   for (size_t i = 0; i < cmdlines.size(); i++) {
66     std::string cmdline = cmdlines[i];  // mutable copy
67     // Add nullbyte to make sure it's a C string.
68     cmdline.resize(cmdline.size() + 1, '\0');
69     char* cmdline_cstr = &(cmdline[0]);
70     ssize_t size = NormalizeCmdLine(&cmdline_cstr, cmdline.size());
71     if (size == -1) {
72       PERFETTO_PLOG("Failed to normalize cmdline %s. Stopping the parse.",
73                     cmdlines[i].c_str());
74       return base::nullopt;
75     }
76     normalized_cmdlines.emplace_back(cmdline_cstr, static_cast<size_t>(size));
77   }
78   return base::make_optional(normalized_cmdlines);
79 }
80 
81 // This is mostly the same as GetHeapprofdProgramProperty in
82 // https://android.googlesource.com/platform/bionic/+/master/libc/bionic/malloc_common.cpp
83 // This should give the same result as GetHeapprofdProgramProperty.
GetCmdlineForPID(pid_t pid,std::string * name)84 bool GetCmdlineForPID(pid_t pid, std::string* name) {
85   std::string filename = "/proc/" + std::to_string(pid) + "/cmdline";
86   base::ScopedFile fd(base::OpenFile(filename, O_RDONLY | O_CLOEXEC));
87   if (!fd) {
88     PERFETTO_DPLOG("Failed to open %s", filename.c_str());
89     return false;
90   }
91   char cmdline[512];
92   const size_t max_read_size = sizeof(cmdline) - 1;
93   ssize_t rd = read(*fd, cmdline, max_read_size);
94   if (rd == -1) {
95     PERFETTO_DPLOG("Failed to read %s", filename.c_str());
96     return false;
97   }
98 
99   if (rd == 0) {
100     PERFETTO_DLOG("Empty cmdline for %" PRIdMAX ". Skipping.",
101                   static_cast<intmax_t>(pid));
102     return false;
103   }
104 
105   // In some buggy kernels (before http://bit.ly/37R7qwL) /proc/pid/cmdline is
106   // not NUL-terminated (see b/147438623). If we read < max_read_size bytes
107   // assume we are hitting the aforementioned kernel bug and terminate anyways.
108   const size_t rd_u = static_cast<size_t>(rd);
109   if (rd_u >= max_read_size && memchr(cmdline, '\0', rd_u) == nullptr) {
110     // We did not manage to read the first argument.
111     PERFETTO_DLOG("Overflow reading cmdline for %" PRIdMAX,
112                   static_cast<intmax_t>(pid));
113     errno = EOVERFLOW;
114     return false;
115   }
116 
117   cmdline[rd] = '\0';
118   char* cmdline_start = cmdline;
119   ssize_t size = NormalizeCmdLine(&cmdline_start, rd_u);
120   if (size == -1)
121     return false;
122   name->assign(cmdline_start, static_cast<size_t>(size));
123   return true;
124 }
125 
FindAllProfilablePids(std::set<pid_t> * pids)126 void FindAllProfilablePids(std::set<pid_t>* pids) {
127   ForEachPid([pids](pid_t pid) {
128     if (pid == getpid())
129       return;
130 
131     char filename_buf[128];
132     if (!GetProcFile(pid, "cmdline", filename_buf, sizeof(filename_buf)))
133       return;
134     struct stat statbuf;
135     // Check if we have permission to the process.
136     if (stat(filename_buf, &statbuf) == 0)
137       pids->emplace(pid);
138   });
139 }
140 
FindPidsForCmdlines(const std::vector<std::string> & cmdlines,std::set<pid_t> * pids)141 void FindPidsForCmdlines(const std::vector<std::string>& cmdlines,
142                          std::set<pid_t>* pids) {
143   ForEachPid([&cmdlines, pids](pid_t pid) {
144     if (pid == getpid())
145       return;
146     std::string process_cmdline;
147     process_cmdline.reserve(512);
148     GetCmdlineForPID(pid, &process_cmdline);
149     for (const std::string& cmdline : cmdlines) {
150       if (process_cmdline == cmdline)
151         pids->emplace(static_cast<pid_t>(pid));
152     }
153   });
154 }
155 
ReadStatus(pid_t pid)156 base::Optional<std::string> ReadStatus(pid_t pid) {
157   std::string path = "/proc/" + std::to_string(pid) + "/status";
158   std::string status;
159   bool read_proc = base::ReadFile(path, &status);
160   if (!read_proc) {
161     PERFETTO_ELOG("Failed to read %s", path.c_str());
162     return base::nullopt;
163   }
164   return base::Optional<std::string>(status);
165 }
166 
GetRssAnonAndSwap(const std::string & status)167 base::Optional<uint32_t> GetRssAnonAndSwap(const std::string& status) {
168   auto anon_rss = ParseProcStatusSize(status, "RssAnon:");
169   auto swap = ParseProcStatusSize(status, "VmSwap:");
170   if (anon_rss.has_value() && swap.has_value()) {
171     return *anon_rss + *swap;
172   }
173   return base::nullopt;
174 }
175 
RemoveUnderAnonThreshold(uint32_t min_size_kb,std::set<pid_t> * pids)176 void RemoveUnderAnonThreshold(uint32_t min_size_kb, std::set<pid_t>* pids) {
177   for (auto it = pids->begin(); it != pids->end();) {
178     const pid_t pid = *it;
179 
180     base::Optional<std::string> status = ReadStatus(pid);
181     base::Optional<uint32_t> rss_and_swap;
182     if (status)
183       rss_and_swap = GetRssAnonAndSwap(*status);
184 
185     if (rss_and_swap && rss_and_swap < min_size_kb) {
186       PERFETTO_LOG("Removing pid %d from profiled set (anon: %d kB < %" PRIu32
187                    ")",
188                    pid, *rss_and_swap, min_size_kb);
189       it = pids->erase(it);
190     } else {
191       ++it;
192     }
193   }
194 }
195 
GetUids(const std::string & status)196 base::Optional<Uids> GetUids(const std::string& status) {
197   auto entry_idx = status.find("Uid:");
198   if (entry_idx == std::string::npos)
199     return base::nullopt;
200 
201   Uids uids;
202   const char* str = &status[entry_idx + 4];
203   char* endptr;
204 
205   uids.real = strtoull(str, &endptr, 10);
206   if (*endptr != ' ' && *endptr != '\t')
207     return base::nullopt;
208 
209   str = endptr;
210   uids.effective = strtoull(str, &endptr, 10);
211   if (*endptr != ' ' && *endptr != '\t')
212     return base::nullopt;
213 
214   str = endptr;
215   uids.saved_set = strtoull(str, &endptr, 10);
216   if (*endptr != ' ' && *endptr != '\t')
217     return base::nullopt;
218 
219   str = endptr;
220   uids.filesystem = strtoull(str, &endptr, 10);
221   if (*endptr != '\n' && *endptr != '\0')
222     return base::nullopt;
223   return uids;
224 }
225 
226 }  // namespace profiling
227 }  // namespace perfetto
228