• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 "simpleperf.h"
18 
19 #include <limits.h>
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <time.h>
27 #include <unistd.h>
28 
29 #include <mutex>
30 #include <sstream>
31 #include <android/log.h>
32 
33 namespace simpleperf {
34 
35 enum RecordCmd {
36   CMD_PAUSE_RECORDING = 1,
37   CMD_RESUME_RECORDING,
38 };
39 
40 class RecordOptionsImpl {
41  public:
42   std::string output_filename;
43   std::string event = "cpu-cycles";
44   size_t freq = 4000;
45   double duration_in_second = 0.0;
46   std::vector<pid_t> threads;
47   bool dwarf_callgraph = false;
48   bool fp_callgraph = false;
49   bool trace_offcpu = false;
50 };
51 
RecordOptions()52 RecordOptions::RecordOptions() : impl_(new RecordOptionsImpl) {
53 }
54 
~RecordOptions()55 RecordOptions::~RecordOptions() {
56   delete impl_;
57 }
58 
SetOutputFilename(const std::string & filename)59 RecordOptions& RecordOptions::SetOutputFilename(const std::string &filename) {
60   impl_->output_filename = filename;
61   return *this;
62 }
63 
SetEvent(const std::string & event)64 RecordOptions& RecordOptions::SetEvent(const std::string &event) {
65   impl_->event = event;
66   return *this;
67 }
68 
SetSampleFrequency(size_t freq)69 RecordOptions& RecordOptions::SetSampleFrequency(size_t freq) {
70   impl_->freq = freq;
71   return *this;
72 }
73 
SetDuration(double duration_in_second)74 RecordOptions& RecordOptions::SetDuration(double duration_in_second) {
75   impl_->duration_in_second = duration_in_second;
76   return *this;
77 }
78 
SetSampleThreads(const std::vector<pid_t> & threads)79 RecordOptions& RecordOptions::SetSampleThreads(const std::vector<pid_t> &threads) {
80   impl_->threads = threads;
81   return *this;
82 }
83 
RecordDwarfCallGraph()84 RecordOptions& RecordOptions::RecordDwarfCallGraph() {
85   impl_->dwarf_callgraph = true;
86   impl_->fp_callgraph = false;
87   return *this;
88 }
89 
RecordFramePointerCallGraph()90 RecordOptions& RecordOptions::RecordFramePointerCallGraph() {
91   impl_->fp_callgraph = true;
92   impl_->dwarf_callgraph = false;
93   return *this;
94 }
95 
TraceOffCpu()96 RecordOptions& RecordOptions::TraceOffCpu() {
97   impl_->trace_offcpu = true;
98   return *this;
99 }
100 
GetDefaultOutputFilename()101 static std::string GetDefaultOutputFilename() {
102   time_t t = time(nullptr);
103   struct tm tm;
104   if (localtime_r(&t, &tm) != &tm) {
105     return "perf.data";
106   }
107   char* buf = nullptr;
108   asprintf(&buf, "perf-%02d-%02d-%02d-%02d-%02d.data", tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
109            tm.tm_min, tm.tm_sec);
110   std::string result = buf;
111   free(buf);
112   return result;
113 }
114 
ToRecordArgs() const115 std::vector<std::string> RecordOptions::ToRecordArgs() const {
116   std::vector<std::string> args;
117   std::string output_filename = impl_->output_filename;
118   if (output_filename.empty()) {
119     output_filename = GetDefaultOutputFilename();
120   }
121   args.insert(args.end(), {"-o", output_filename});
122   args.insert(args.end(), {"-e", impl_->event});
123   args.insert(args.end(), {"-f", std::to_string(impl_->freq)});
124   if (impl_->duration_in_second != 0.0) {
125     args.insert(args.end(), {"--duration", std::to_string(impl_->duration_in_second)});
126   }
127   if (impl_->threads.empty()) {
128     args.insert(args.end(), {"-p", std::to_string(getpid())});
129   } else {
130     std::ostringstream os;
131     os << *(impl_->threads.begin());
132     for (auto it = std::next(impl_->threads.begin()); it != impl_->threads.end(); ++it) {
133       os << "," << *it;
134     }
135     args.insert(args.end(), {"-t", os.str()});
136   }
137   if (impl_->dwarf_callgraph) {
138     args.push_back("-g");
139   } else if (impl_->fp_callgraph) {
140     args.insert(args.end(), {"--call-graph", "fp"});
141   }
142   if (impl_->trace_offcpu) {
143     args.push_back("--trace-offcpu");
144   }
145   return args;
146 }
147 
Abort(const char * fmt,...)148 static void Abort(const char* fmt, ...) {
149   va_list vl;
150   va_start(vl, fmt);
151   __android_log_vprint(ANDROID_LOG_FATAL, "simpleperf", fmt, vl);
152   va_end(vl);
153   abort();
154 }
155 
156 class ProfileSessionImpl {
157  public:
ProfileSessionImpl(const std::string & app_data_dir)158   ProfileSessionImpl(const std::string& app_data_dir)
159       : app_data_dir_(app_data_dir),
160         simpleperf_data_dir_(app_data_dir + "/simpleperf_data") {}
161   ~ProfileSessionImpl();
162   void StartRecording(const std::vector<std::string>& args);
163   void PauseRecording();
164   void ResumeRecording();
165   void StopRecording();
166 
167  private:
168   std::string FindSimpleperf();
169   std::string FindSimpleperfInTempDir();
170   void CheckIfPerfEnabled();
171   void CreateSimpleperfDataDir();
172   void CreateSimpleperfProcess(const std::string& simpleperf_path,
173                                const std::vector<std::string>& record_args);
174   void SendCmd(const std::string& cmd);
175   std::string ReadReply();
176 
177   enum State {
178     NOT_YET_STARTED,
179     STARTED,
180     PAUSED,
181     STOPPED,
182   };
183 
184   const std::string app_data_dir_;
185   const std::string simpleperf_data_dir_;
186   std::mutex lock_;  // Protect all members below.
187   State state_ = NOT_YET_STARTED;
188   pid_t simpleperf_pid_ = -1;
189   int control_fd_ = -1;
190   int reply_fd_ = -1;
191 };
192 
~ProfileSessionImpl()193 ProfileSessionImpl::~ProfileSessionImpl() {
194   if (control_fd_ != -1) {
195     close(control_fd_);
196   }
197   if (reply_fd_ != -1) {
198     close(reply_fd_);
199   }
200 }
201 
StartRecording(const std::vector<std::string> & args)202 void ProfileSessionImpl::StartRecording(const std::vector<std::string> &args) {
203   std::lock_guard<std::mutex> guard(lock_);
204   if (state_ != NOT_YET_STARTED) {
205     Abort("startRecording: session in wrong state %d", state_);
206   }
207   std::string simpleperf_path = FindSimpleperf();
208   CheckIfPerfEnabled();
209   CreateSimpleperfDataDir();
210   CreateSimpleperfProcess(simpleperf_path, args);
211   state_ = STARTED;
212 }
213 
PauseRecording()214 void ProfileSessionImpl::PauseRecording() {
215   std::lock_guard<std::mutex> guard(lock_);
216   if (state_ != STARTED) {
217     Abort("pauseRecording: session in wrong state %d", state_);
218   }
219   SendCmd("pause");
220   state_ = PAUSED;
221 }
222 
ResumeRecording()223 void ProfileSessionImpl::ResumeRecording() {
224   std::lock_guard<std::mutex> guard(lock_);
225   if (state_ != PAUSED) {
226     Abort("resumeRecording: session in wrong state %d", state_);
227   }
228   SendCmd("resume");
229   state_ = STARTED;
230 }
231 
StopRecording()232 void ProfileSessionImpl::StopRecording() {
233   std::lock_guard<std::mutex> guard(lock_);
234   if (state_ != STARTED && state_ != PAUSED) {
235     Abort("stopRecording: session in wrong state %d", state_);
236   }
237   // Send SIGINT to simpleperf to stop recording.
238   if (kill(simpleperf_pid_, SIGINT) == -1) {
239     Abort("failed to stop simpleperf: %s", strerror(errno));
240   }
241   int status;
242   pid_t result = TEMP_FAILURE_RETRY(waitpid(simpleperf_pid_, &status, 0));
243   if (result == -1) {
244     Abort("failed to call waitpid: %s", strerror(errno));
245   }
246   if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
247     Abort("simpleperf exited with error, status = 0x%x", status);
248   }
249   state_ = STOPPED;
250 }
251 
SendCmd(const std::string & cmd)252 void ProfileSessionImpl::SendCmd(const std::string& cmd) {
253   std::string data = cmd + "\n";
254   if (TEMP_FAILURE_RETRY(write(control_fd_, &data[0], data.size())) !=
255                          static_cast<ssize_t>(data.size())) {
256     Abort("failed to send cmd to simpleperf: %s", strerror(errno));
257   }
258   if (ReadReply() != "ok") {
259     Abort("failed to run cmd in simpleperf: %s", cmd.c_str());
260   }
261 }
262 
IsExecutableFile(const std::string & path)263 static bool IsExecutableFile(const std::string& path) {
264   struct stat st;
265   if (stat(path.c_str(), &st) == 0) {
266     if (S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR)) {
267       return true;
268     }
269   }
270   return false;
271 }
272 
FindSimpleperf()273 std::string ProfileSessionImpl::FindSimpleperf() {
274   // 1. Try /data/local/tmp/simpleperf first. Probably it's newer than /system/bin/simpleperf.
275   std::string simpleperf_path = FindSimpleperfInTempDir();
276   if (!simpleperf_path.empty()) {
277     return simpleperf_path;
278   }
279   // 2. Try /system/bin/simpleperf, which is available on Android >= Q.
280   simpleperf_path = "/system/bin/simpleperf";
281   if (IsExecutableFile(simpleperf_path)) {
282     return simpleperf_path;
283   }
284   Abort("can't find simpleperf on device. Please run api_profiler.py.");
285   return "";
286 }
287 
FindSimpleperfInTempDir()288 std::string ProfileSessionImpl::FindSimpleperfInTempDir() {
289   const std::string path = "/data/local/tmp/simpleperf";
290   if (!IsExecutableFile(path)) {
291     return "";
292   }
293   // Copy it to app_dir to execute it.
294   const std::string to_path = app_data_dir_ + "/simpleperf";
295   const std::string copy_cmd = "cp " + path + " " + to_path;
296   if (system(copy_cmd.c_str()) != 0) {
297     return "";
298   }
299   const std::string test_cmd = to_path;
300   // For apps with target sdk >= 29, executing app data file isn't allowed. So test executing it.
301   if (system(test_cmd.c_str()) != 0) {
302     return "";
303   }
304   return to_path;
305 }
306 
ReadFile(FILE * fp)307 static std::string ReadFile(FILE* fp) {
308   std::string s;
309   char buf[200];
310   while (true) {
311     ssize_t n = fread(buf, 1, sizeof(buf), fp);
312     if (n <= 0) {
313       break;
314     }
315     s.insert(s.end(), buf, buf + n);
316   }
317   return s;
318 }
319 
CheckIfPerfEnabled()320 void ProfileSessionImpl::CheckIfPerfEnabled() {
321   FILE* fp = popen("/system/bin/getprop security.perf_harden", "re");
322   if (fp == nullptr) {
323     return;  // Omit check if getprop doesn't exist.
324   }
325   std::string s = ReadFile(fp);
326   pclose(fp);
327   if (!s.empty() && s[0] == '1') {
328     Abort("linux perf events aren't enabled on the device. Please run api_profiler.py.");
329   }
330 }
331 
CreateSimpleperfDataDir()332 void ProfileSessionImpl::CreateSimpleperfDataDir() {
333   struct stat st;
334   if (stat(simpleperf_data_dir_.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
335     return;
336   }
337   if (mkdir(simpleperf_data_dir_.c_str(), 0700) == -1) {
338     Abort("failed to create simpleperf data dir %s: %s", simpleperf_data_dir_.c_str(),
339           strerror(errno));
340   }
341 }
342 
CreateSimpleperfProcess(const std::string & simpleperf_path,const std::vector<std::string> & record_args)343 void ProfileSessionImpl::CreateSimpleperfProcess(const std::string &simpleperf_path,
344                                                  const std::vector<std::string> &record_args) {
345   // 1. Create control/reply pips.
346   int control_fd[2];
347   int reply_fd[2];
348   if (pipe(control_fd) != 0 || pipe(reply_fd) != 0) {
349     Abort("failed to call pipe: %s", strerror(errno));
350   }
351 
352   // 2. Prepare simpleperf arguments.
353   std::vector<std::string> args;
354   args.emplace_back(simpleperf_path);
355   args.emplace_back("record");
356   args.emplace_back("--log-to-android-buffer");
357   args.insert(args.end(), {"--log", "debug"});
358   args.emplace_back("--stdio-controls-profiling");
359   args.emplace_back("--in-app");
360   args.insert(args.end(), {"--tracepoint-events", "/data/local/tmp/tracepoint_events"});
361   args.insert(args.end(), record_args.begin(), record_args.end());
362   char* argv[args.size() + 1];
363   for (size_t i = 0; i < args.size(); ++i) {
364     argv[i] = &args[i][0];
365   }
366   argv[args.size()] = nullptr;
367 
368   // 3. Start simpleperf process.
369   int pid = fork();
370   if (pid == -1) {
371     Abort("failed to fork: %s", strerror(errno));
372   }
373   if (pid == 0) {
374     // child process
375     close(control_fd[1]);
376     dup2(control_fd[0], 0);  // simpleperf read control cmd from fd 0.
377     close(control_fd[0]);
378     close(reply_fd[0]);
379     dup2(reply_fd[1], 1);  // simpleperf writes reply to fd 1.
380     close(reply_fd[0]);
381     chdir(simpleperf_data_dir_.c_str());
382     execvp(argv[0], argv);
383     Abort("failed to call exec: %s", strerror(errno));
384   }
385   // parent process
386   close(control_fd[0]);
387   control_fd_ = control_fd[1];
388   close(reply_fd[1]);
389   reply_fd_ = reply_fd[0];
390   simpleperf_pid_ = pid;
391 
392   // 4. Wait until simpleperf starts recording.
393   std::string start_flag = ReadReply();
394   if (start_flag != "started") {
395     Abort("failed to receive simpleperf start flag");
396   }
397 }
398 
ReadReply()399 std::string ProfileSessionImpl::ReadReply() {
400   std::string s;
401   while (true) {
402     char c;
403     ssize_t result = TEMP_FAILURE_RETRY(read(reply_fd_, &c, 1));
404     if (result <= 0 || c == '\n') {
405       break;
406     }
407     s.push_back(c);
408   }
409   return s;
410 }
411 
ProfileSession()412 ProfileSession::ProfileSession() {
413   FILE* fp = fopen("/proc/self/cmdline", "r");
414   if (fp == nullptr) {
415     Abort("failed to open /proc/self/cmdline: %s", strerror(errno));
416   }
417   std::string s = ReadFile(fp);
418   fclose(fp);
419   for (int i = 0; i < s.size(); i++) {
420     if (s[i] == '\0') {
421       s = s.substr(0, i);
422       break;
423     }
424   }
425   std::string app_data_dir = "/data/data/" + s;
426   impl_ = new ProfileSessionImpl(app_data_dir);
427 }
428 
ProfileSession(const std::string & app_data_dir)429 ProfileSession::ProfileSession(const std::string& app_data_dir)
430     : impl_(new ProfileSessionImpl(app_data_dir)) {}
431 
~ProfileSession()432 ProfileSession::~ProfileSession() {
433   delete impl_;
434 }
435 
StartRecording(const RecordOptions & options)436 void ProfileSession::StartRecording(const RecordOptions &options) {
437   StartRecording(options.ToRecordArgs());
438 }
439 
StartRecording(const std::vector<std::string> & record_args)440 void ProfileSession::StartRecording(const std::vector<std::string> &record_args) {
441    impl_->StartRecording(record_args);
442 }
443 
PauseRecording()444 void ProfileSession::PauseRecording() {
445   impl_->PauseRecording();
446 }
447 
ResumeRecording()448 void ProfileSession::ResumeRecording() {
449   impl_->ResumeRecording();
450 }
451 
StopRecording()452 void ProfileSession::StopRecording() {
453   impl_->StopRecording();
454 }
455 
456 }  // namespace simpleperf
457