• 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 <android/log.h>
30 #include <mutex>
31 #include <sstream>
32 
33 namespace simpleperf {
34 
35 constexpr int AID_USER_OFFSET = 100000;
36 
37 enum RecordCmd {
38   CMD_PAUSE_RECORDING = 1,
39   CMD_RESUME_RECORDING,
40 };
41 
42 class RecordOptionsImpl {
43  public:
44   std::string output_filename;
45   std::string event = "cpu-cycles";
46   size_t freq = 4000;
47   double duration_in_second = 0.0;
48   std::vector<pid_t> threads;
49   bool dwarf_callgraph = false;
50   bool fp_callgraph = false;
51   bool trace_offcpu = false;
52 };
53 
RecordOptions()54 RecordOptions::RecordOptions() : impl_(new RecordOptionsImpl) {}
55 
~RecordOptions()56 RecordOptions::~RecordOptions() {
57   delete impl_;
58 }
59 
SetOutputFilename(const std::string & filename)60 RecordOptions& RecordOptions::SetOutputFilename(const std::string& filename) {
61   impl_->output_filename = filename;
62   return *this;
63 }
64 
SetEvent(const std::string & event)65 RecordOptions& RecordOptions::SetEvent(const std::string& event) {
66   impl_->event = event;
67   return *this;
68 }
69 
SetSampleFrequency(size_t freq)70 RecordOptions& RecordOptions::SetSampleFrequency(size_t freq) {
71   impl_->freq = freq;
72   return *this;
73 }
74 
SetDuration(double duration_in_second)75 RecordOptions& RecordOptions::SetDuration(double duration_in_second) {
76   impl_->duration_in_second = duration_in_second;
77   return *this;
78 }
79 
SetSampleThreads(const std::vector<pid_t> & threads)80 RecordOptions& RecordOptions::SetSampleThreads(const std::vector<pid_t>& threads) {
81   impl_->threads = threads;
82   return *this;
83 }
84 
RecordDwarfCallGraph()85 RecordOptions& RecordOptions::RecordDwarfCallGraph() {
86   impl_->dwarf_callgraph = true;
87   impl_->fp_callgraph = false;
88   return *this;
89 }
90 
RecordFramePointerCallGraph()91 RecordOptions& RecordOptions::RecordFramePointerCallGraph() {
92   impl_->fp_callgraph = true;
93   impl_->dwarf_callgraph = false;
94   return *this;
95 }
96 
TraceOffCpu()97 RecordOptions& RecordOptions::TraceOffCpu() {
98   impl_->trace_offcpu = true;
99   return *this;
100 }
101 
GetDefaultOutputFilename()102 static std::string GetDefaultOutputFilename() {
103   time_t t = time(nullptr);
104   struct tm tm;
105   if (localtime_r(&t, &tm) != &tm) {
106     return "perf.data";
107   }
108   char* buf = nullptr;
109   asprintf(&buf, "perf-%02d-%02d-%02d-%02d-%02d.data", tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
110            tm.tm_min, tm.tm_sec);
111   std::string result = buf;
112   free(buf);
113   return result;
114 }
115 
ToRecordArgs() const116 std::vector<std::string> RecordOptions::ToRecordArgs() const {
117   std::vector<std::string> args;
118   std::string output_filename = impl_->output_filename;
119   if (output_filename.empty()) {
120     output_filename = GetDefaultOutputFilename();
121   }
122   args.insert(args.end(), {"-o", output_filename});
123   args.insert(args.end(), {"-e", impl_->event});
124   args.insert(args.end(), {"-f", std::to_string(impl_->freq)});
125   if (impl_->duration_in_second != 0.0) {
126     args.insert(args.end(), {"--duration", std::to_string(impl_->duration_in_second)});
127   }
128   if (impl_->threads.empty()) {
129     args.insert(args.end(), {"-p", std::to_string(getpid())});
130   } else {
131     std::ostringstream os;
132     os << *(impl_->threads.begin());
133     for (auto it = std::next(impl_->threads.begin()); it != impl_->threads.end(); ++it) {
134       os << "," << *it;
135     }
136     args.insert(args.end(), {"-t", os.str()});
137   }
138   if (impl_->dwarf_callgraph) {
139     args.push_back("-g");
140   } else if (impl_->fp_callgraph) {
141     args.insert(args.end(), {"--call-graph", "fp"});
142   }
143   if (impl_->trace_offcpu) {
144     args.push_back("--trace-offcpu");
145   }
146   return args;
147 }
148 
Abort(const char * fmt,...)149 static void Abort(const char* fmt, ...) {
150   va_list vl;
151   va_start(vl, fmt);
152   __android_log_vprint(ANDROID_LOG_FATAL, "simpleperf", fmt, vl);
153   va_end(vl);
154   abort();
155 }
156 
157 class ProfileSessionImpl {
158  public:
ProfileSessionImpl(const std::string & app_data_dir)159   ProfileSessionImpl(const std::string& app_data_dir)
160       : app_data_dir_(app_data_dir), 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   void CheckIfPerfEnabled();
170   std::string GetProperty(const std::string& name);
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   bool trace_offcpu_ = false;
192 };
193 
~ProfileSessionImpl()194 ProfileSessionImpl::~ProfileSessionImpl() {
195   if (control_fd_ != -1) {
196     close(control_fd_);
197   }
198   if (reply_fd_ != -1) {
199     close(reply_fd_);
200   }
201 }
202 
StartRecording(const std::vector<std::string> & args)203 void ProfileSessionImpl::StartRecording(const std::vector<std::string>& args) {
204   std::lock_guard<std::mutex> guard(lock_);
205   if (state_ != NOT_YET_STARTED) {
206     Abort("startRecording: session in wrong state %d", state_);
207   }
208   for (const auto& arg : args) {
209     if (arg == "--trace-offcpu") {
210       trace_offcpu_ = true;
211     }
212   }
213   std::string simpleperf_path = FindSimpleperf();
214   CheckIfPerfEnabled();
215   CreateSimpleperfDataDir();
216   CreateSimpleperfProcess(simpleperf_path, args);
217   state_ = STARTED;
218 }
219 
PauseRecording()220 void ProfileSessionImpl::PauseRecording() {
221   std::lock_guard<std::mutex> guard(lock_);
222   if (state_ != STARTED) {
223     Abort("pauseRecording: session in wrong state %d", state_);
224   }
225   if (trace_offcpu_) {
226     Abort("--trace-offcpu doesn't work well with pause/resume recording");
227   }
228   SendCmd("pause");
229   state_ = PAUSED;
230 }
231 
ResumeRecording()232 void ProfileSessionImpl::ResumeRecording() {
233   std::lock_guard<std::mutex> guard(lock_);
234   if (state_ != PAUSED) {
235     Abort("resumeRecording: session in wrong state %d", state_);
236   }
237   SendCmd("resume");
238   state_ = STARTED;
239 }
240 
StopRecording()241 void ProfileSessionImpl::StopRecording() {
242   std::lock_guard<std::mutex> guard(lock_);
243   if (state_ != STARTED && state_ != PAUSED) {
244     Abort("stopRecording: session in wrong state %d", state_);
245   }
246   // Send SIGINT to simpleperf to stop recording.
247   if (kill(simpleperf_pid_, SIGINT) == -1) {
248     Abort("failed to stop simpleperf: %s", strerror(errno));
249   }
250   int status;
251   pid_t result = TEMP_FAILURE_RETRY(waitpid(simpleperf_pid_, &status, 0));
252   if (result == -1) {
253     Abort("failed to call waitpid: %s", strerror(errno));
254   }
255   if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
256     Abort("simpleperf exited with error, status = 0x%x", status);
257   }
258   state_ = STOPPED;
259 }
260 
SendCmd(const std::string & cmd)261 void ProfileSessionImpl::SendCmd(const std::string& cmd) {
262   std::string data = cmd + "\n";
263   if (TEMP_FAILURE_RETRY(write(control_fd_, &data[0], data.size())) !=
264       static_cast<ssize_t>(data.size())) {
265     Abort("failed to send cmd to simpleperf: %s", strerror(errno));
266   }
267   if (ReadReply() != "ok") {
268     Abort("failed to run cmd in simpleperf: %s", cmd.c_str());
269   }
270 }
271 
IsExecutableFile(const std::string & path)272 static bool IsExecutableFile(const std::string& path) {
273   struct stat st;
274   if (stat(path.c_str(), &st) == 0) {
275     if (S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR)) {
276       return true;
277     }
278   }
279   return false;
280 }
281 
ReadFile(FILE * fp)282 static std::string ReadFile(FILE* fp) {
283   std::string s;
284   if (fp == nullptr) {
285     return s;
286   }
287   char buf[200];
288   while (true) {
289     ssize_t n = fread(buf, 1, sizeof(buf), fp);
290     if (n <= 0) {
291       break;
292     }
293     s.insert(s.end(), buf, buf + n);
294   }
295   fclose(fp);
296   return s;
297 }
298 
RunCmd(std::vector<const char * > args,std::string * stdout)299 static bool RunCmd(std::vector<const char*> args, std::string* stdout) {
300   int stdout_fd[2];
301   if (pipe(stdout_fd) != 0) {
302     return false;
303   }
304   args.push_back(nullptr);
305   // Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
306   // So we use vfork instead of fork to avoid calling them.
307   int pid = vfork();
308   if (pid == -1) {
309     return false;
310   }
311   if (pid == 0) {
312     // child process
313     close(stdout_fd[0]);
314     dup2(stdout_fd[1], 1);
315     close(stdout_fd[1]);
316     execvp(const_cast<char*>(args[0]), const_cast<char**>(args.data()));
317     _exit(1);
318   }
319   // parent process
320   close(stdout_fd[1]);
321   int status;
322   pid_t result = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
323   if (result == -1) {
324     Abort("failed to call waitpid: %s", strerror(errno));
325   }
326   if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
327     return false;
328   }
329   if (stdout == nullptr) {
330     close(stdout_fd[0]);
331   } else {
332     *stdout = ReadFile(fdopen(stdout_fd[0], "r"));
333   }
334   return true;
335 }
336 
FindSimpleperf()337 std::string ProfileSessionImpl::FindSimpleperf() {
338   // Try /system/bin/simpleperf, which is available on Android >= Q.
339   simpleperf_path = "/system/bin/simpleperf";
340   if (IsExecutableFile(simpleperf_path)) {
341     return simpleperf_path;
342   }
343   Abort("can't find simpleperf on device. Please run api_profiler.py.");
344   return "";
345 }
346 
CheckIfPerfEnabled()347 void ProfileSessionImpl::CheckIfPerfEnabled() {
348   if (GetProperty("persist.simpleperf.profile_app_uid") == std::to_string(getuid())) {
349     std::string time_str = GetProperty("persist.simpleperf.profile_app_expiration_time");
350     if (!time_str.empty()) {
351       errno = 0;
352       uint64_t expiration_time = strtoull(time_str.data(), nullptr, 10);
353       if (errno == 0 && expiration_time > time(nullptr)) {
354         return;
355       }
356     }
357   }
358   if (GetProperty("security.perf_harden") == "1") {
359     Abort("Recording app isn't enabled on the device. Please run api_profiler.py.");
360   }
361 }
362 
GetProperty(const std::string & name)363 std::string ProfileSessionImpl::GetProperty(const std::string& name) {
364   std::string s;
365   if (!RunCmd({"/system/bin/getprop", name.c_str()}, &s)) {
366     return "";
367   }
368   return s;
369 }
370 
CreateSimpleperfDataDir()371 void ProfileSessionImpl::CreateSimpleperfDataDir() {
372   struct stat st;
373   if (stat(simpleperf_data_dir_.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
374     return;
375   }
376   if (mkdir(simpleperf_data_dir_.c_str(), 0700) == -1) {
377     Abort("failed to create simpleperf data dir %s: %s", simpleperf_data_dir_.c_str(),
378           strerror(errno));
379   }
380 }
381 
CreateSimpleperfProcess(const std::string & simpleperf_path,const std::vector<std::string> & record_args)382 void ProfileSessionImpl::CreateSimpleperfProcess(const std::string& simpleperf_path,
383                                                  const std::vector<std::string>& record_args) {
384   // 1. Create control/reply pips.
385   int control_fd[2];
386   int reply_fd[2];
387   if (pipe(control_fd) != 0 || pipe(reply_fd) != 0) {
388     Abort("failed to call pipe: %s", strerror(errno));
389   }
390 
391   // 2. Prepare simpleperf arguments.
392   std::vector<std::string> args;
393   args.emplace_back(simpleperf_path);
394   args.emplace_back("record");
395   args.emplace_back("--log-to-android-buffer");
396   args.insert(args.end(), {"--log", "debug"});
397   args.emplace_back("--stdio-controls-profiling");
398   args.emplace_back("--in-app");
399   args.insert(args.end(), {"--tracepoint-events", "/data/local/tmp/tracepoint_events"});
400   args.insert(args.end(), record_args.begin(), record_args.end());
401   char* argv[args.size() + 1];
402   for (size_t i = 0; i < args.size(); ++i) {
403     argv[i] = &args[i][0];
404   }
405   argv[args.size()] = nullptr;
406 
407   // 3. Start simpleperf process.
408   // Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
409   // So we use vfork instead of fork to avoid calling them.
410   int pid = vfork();
411   if (pid == -1) {
412     Abort("failed to fork: %s", strerror(errno));
413   }
414   if (pid == 0) {
415     // child process
416     close(control_fd[1]);
417     dup2(control_fd[0], 0);  // simpleperf read control cmd from fd 0.
418     close(control_fd[0]);
419     close(reply_fd[0]);
420     dup2(reply_fd[1], 1);  // simpleperf writes reply to fd 1.
421     close(reply_fd[0]);
422     chdir(simpleperf_data_dir_.c_str());
423     execvp(argv[0], argv);
424     Abort("failed to call exec: %s", strerror(errno));
425   }
426   // parent process
427   close(control_fd[0]);
428   control_fd_ = control_fd[1];
429   close(reply_fd[1]);
430   reply_fd_ = reply_fd[0];
431   simpleperf_pid_ = pid;
432 
433   // 4. Wait until simpleperf starts recording.
434   std::string start_flag = ReadReply();
435   if (start_flag != "started") {
436     Abort("failed to receive simpleperf start flag");
437   }
438 }
439 
ReadReply()440 std::string ProfileSessionImpl::ReadReply() {
441   std::string s;
442   while (true) {
443     char c;
444     ssize_t result = TEMP_FAILURE_RETRY(read(reply_fd_, &c, 1));
445     if (result <= 0 || c == '\n') {
446       break;
447     }
448     s.push_back(c);
449   }
450   return s;
451 }
452 
ProfileSession()453 ProfileSession::ProfileSession() {
454   FILE* fp = fopen("/proc/self/cmdline", "r");
455   if (fp == nullptr) {
456     Abort("failed to open /proc/self/cmdline: %s", strerror(errno));
457   }
458   std::string s = ReadFile(fp);
459   for (int i = 0; i < s.size(); i++) {
460     if (s[i] == '\0') {
461       s = s.substr(0, i);
462       break;
463     }
464   }
465   std::string app_data_dir = "/data/data/" + s;
466   int uid = getuid();
467   if (uid >= AID_USER_OFFSET) {
468     int user_id = uid / AID_USER_OFFSET;
469     app_data_dir = "/data/user/" + std::to_string(user_id) + "/" + s;
470   }
471   impl_ = new ProfileSessionImpl(app_data_dir);
472 }
473 
ProfileSession(const std::string & app_data_dir)474 ProfileSession::ProfileSession(const std::string& app_data_dir)
475     : impl_(new ProfileSessionImpl(app_data_dir)) {}
476 
~ProfileSession()477 ProfileSession::~ProfileSession() {
478   delete impl_;
479 }
480 
StartRecording(const RecordOptions & options)481 void ProfileSession::StartRecording(const RecordOptions& options) {
482   StartRecording(options.ToRecordArgs());
483 }
484 
StartRecording(const std::vector<std::string> & record_args)485 void ProfileSession::StartRecording(const std::vector<std::string>& record_args) {
486   impl_->StartRecording(record_args);
487 }
488 
PauseRecording()489 void ProfileSession::PauseRecording() {
490   impl_->PauseRecording();
491 }
492 
ResumeRecording()493 void ProfileSession::ResumeRecording() {
494   impl_->ResumeRecording();
495 }
496 
StopRecording()497 void ProfileSession::StopRecording() {
498   impl_->StopRecording();
499 }
500 
501 }  // namespace simpleperf
502