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 std::string FindSimpleperfInTempDir();
170 void CheckIfPerfEnabled();
171 std::string GetProperty(const std::string& name);
172 void CreateSimpleperfDataDir();
173 void CreateSimpleperfProcess(const std::string& simpleperf_path,
174 const std::vector<std::string>& record_args);
175 void SendCmd(const std::string& cmd);
176 std::string ReadReply();
177
178 enum State {
179 NOT_YET_STARTED,
180 STARTED,
181 PAUSED,
182 STOPPED,
183 };
184
185 const std::string app_data_dir_;
186 const std::string simpleperf_data_dir_;
187 std::mutex lock_; // Protect all members below.
188 State state_ = NOT_YET_STARTED;
189 pid_t simpleperf_pid_ = -1;
190 int control_fd_ = -1;
191 int reply_fd_ = -1;
192 bool trace_offcpu_ = false;
193 };
194
~ProfileSessionImpl()195 ProfileSessionImpl::~ProfileSessionImpl() {
196 if (control_fd_ != -1) {
197 close(control_fd_);
198 }
199 if (reply_fd_ != -1) {
200 close(reply_fd_);
201 }
202 }
203
StartRecording(const std::vector<std::string> & args)204 void ProfileSessionImpl::StartRecording(const std::vector<std::string>& args) {
205 std::lock_guard<std::mutex> guard(lock_);
206 if (state_ != NOT_YET_STARTED) {
207 Abort("startRecording: session in wrong state %d", state_);
208 }
209 for (const auto& arg : args) {
210 if (arg == "--trace-offcpu") {
211 trace_offcpu_ = true;
212 }
213 }
214 std::string simpleperf_path = FindSimpleperf();
215 CheckIfPerfEnabled();
216 CreateSimpleperfDataDir();
217 CreateSimpleperfProcess(simpleperf_path, args);
218 state_ = STARTED;
219 }
220
PauseRecording()221 void ProfileSessionImpl::PauseRecording() {
222 std::lock_guard<std::mutex> guard(lock_);
223 if (state_ != STARTED) {
224 Abort("pauseRecording: session in wrong state %d", state_);
225 }
226 if (trace_offcpu_) {
227 Abort("--trace-offcpu doesn't work well with pause/resume recording");
228 }
229 SendCmd("pause");
230 state_ = PAUSED;
231 }
232
ResumeRecording()233 void ProfileSessionImpl::ResumeRecording() {
234 std::lock_guard<std::mutex> guard(lock_);
235 if (state_ != PAUSED) {
236 Abort("resumeRecording: session in wrong state %d", state_);
237 }
238 SendCmd("resume");
239 state_ = STARTED;
240 }
241
StopRecording()242 void ProfileSessionImpl::StopRecording() {
243 std::lock_guard<std::mutex> guard(lock_);
244 if (state_ != STARTED && state_ != PAUSED) {
245 Abort("stopRecording: session in wrong state %d", state_);
246 }
247 // Send SIGINT to simpleperf to stop recording.
248 if (kill(simpleperf_pid_, SIGINT) == -1) {
249 Abort("failed to stop simpleperf: %s", strerror(errno));
250 }
251 int status;
252 pid_t result = TEMP_FAILURE_RETRY(waitpid(simpleperf_pid_, &status, 0));
253 if (result == -1) {
254 Abort("failed to call waitpid: %s", strerror(errno));
255 }
256 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
257 Abort("simpleperf exited with error, status = 0x%x", status);
258 }
259 state_ = STOPPED;
260 }
261
SendCmd(const std::string & cmd)262 void ProfileSessionImpl::SendCmd(const std::string& cmd) {
263 std::string data = cmd + "\n";
264 if (TEMP_FAILURE_RETRY(write(control_fd_, &data[0], data.size())) !=
265 static_cast<ssize_t>(data.size())) {
266 Abort("failed to send cmd to simpleperf: %s", strerror(errno));
267 }
268 if (ReadReply() != "ok") {
269 Abort("failed to run cmd in simpleperf: %s", cmd.c_str());
270 }
271 }
272
IsExecutableFile(const std::string & path)273 static bool IsExecutableFile(const std::string& path) {
274 struct stat st;
275 if (stat(path.c_str(), &st) == 0) {
276 if (S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR)) {
277 return true;
278 }
279 }
280 return false;
281 }
282
ReadFile(FILE * fp)283 static std::string ReadFile(FILE* fp) {
284 std::string s;
285 if (fp == nullptr) {
286 return s;
287 }
288 char buf[200];
289 while (true) {
290 ssize_t n = fread(buf, 1, sizeof(buf), fp);
291 if (n <= 0) {
292 break;
293 }
294 s.insert(s.end(), buf, buf + n);
295 }
296 fclose(fp);
297 return s;
298 }
299
RunCmd(std::vector<const char * > args,std::string * stdout)300 static bool RunCmd(std::vector<const char*> args, std::string* stdout) {
301 int stdout_fd[2];
302 if (pipe(stdout_fd) != 0) {
303 return false;
304 }
305 args.push_back(nullptr);
306 // Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
307 // So we use vfork instead of fork to avoid calling them.
308 int pid = vfork();
309 if (pid == -1) {
310 return false;
311 }
312 if (pid == 0) {
313 // child process
314 close(stdout_fd[0]);
315 dup2(stdout_fd[1], 1);
316 close(stdout_fd[1]);
317 execvp(const_cast<char*>(args[0]), const_cast<char**>(args.data()));
318 _exit(1);
319 }
320 // parent process
321 close(stdout_fd[1]);
322 int status;
323 pid_t result = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
324 if (result == -1) {
325 Abort("failed to call waitpid: %s", strerror(errno));
326 }
327 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
328 return false;
329 }
330 if (stdout == nullptr) {
331 close(stdout_fd[0]);
332 } else {
333 *stdout = ReadFile(fdopen(stdout_fd[0], "r"));
334 }
335 return true;
336 }
337
FindSimpleperf()338 std::string ProfileSessionImpl::FindSimpleperf() {
339 // 1. Try /data/local/tmp/simpleperf first. Probably it's newer than /system/bin/simpleperf.
340 std::string simpleperf_path = FindSimpleperfInTempDir();
341 if (!simpleperf_path.empty()) {
342 return simpleperf_path;
343 }
344 // 2. Try /system/bin/simpleperf, which is available on Android >= Q.
345 simpleperf_path = "/system/bin/simpleperf";
346 if (IsExecutableFile(simpleperf_path)) {
347 return simpleperf_path;
348 }
349 Abort("can't find simpleperf on device. Please run api_profiler.py.");
350 return "";
351 }
352
FindSimpleperfInTempDir()353 std::string ProfileSessionImpl::FindSimpleperfInTempDir() {
354 const std::string path = "/data/local/tmp/simpleperf";
355 if (!IsExecutableFile(path)) {
356 return "";
357 }
358 // Copy it to app_dir to execute it.
359 const std::string to_path = app_data_dir_ + "/simpleperf";
360 if (!RunCmd({"/system/bin/cp", path.c_str(), to_path.c_str()}, nullptr)) {
361 return "";
362 }
363 // For apps with target sdk >= 29, executing app data file isn't allowed.
364 // For android R, app context isn't allowed to use perf_event_open.
365 // So test executing downloaded simpleperf.
366 std::string s;
367 if (!RunCmd({to_path.c_str(), "list", "sw"}, &s)) {
368 return "";
369 }
370 if (s.find("cpu-clock") == std::string::npos) {
371 return "";
372 }
373 return to_path;
374 }
375
CheckIfPerfEnabled()376 void ProfileSessionImpl::CheckIfPerfEnabled() {
377 if (GetProperty("persist.simpleperf.profile_app_uid") == std::to_string(getuid())) {
378 std::string time_str = GetProperty("persist.simpleperf.profile_app_expiration_time");
379 if (!time_str.empty()) {
380 errno = 0;
381 uint64_t expiration_time = strtoull(time_str.data(), nullptr, 10);
382 if (errno == 0 && expiration_time > time(nullptr)) {
383 return;
384 }
385 }
386 }
387 if (GetProperty("security.perf_harden") == "1") {
388 Abort("Recording app isn't enabled on the device. Please run api_profiler.py.");
389 }
390 }
391
GetProperty(const std::string & name)392 std::string ProfileSessionImpl::GetProperty(const std::string& name) {
393 std::string s;
394 if (!RunCmd({"/system/bin/getprop", name.c_str()}, &s)) {
395 return "";
396 }
397 return s;
398 }
399
CreateSimpleperfDataDir()400 void ProfileSessionImpl::CreateSimpleperfDataDir() {
401 struct stat st;
402 if (stat(simpleperf_data_dir_.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
403 return;
404 }
405 if (mkdir(simpleperf_data_dir_.c_str(), 0700) == -1) {
406 Abort("failed to create simpleperf data dir %s: %s", simpleperf_data_dir_.c_str(),
407 strerror(errno));
408 }
409 }
410
CreateSimpleperfProcess(const std::string & simpleperf_path,const std::vector<std::string> & record_args)411 void ProfileSessionImpl::CreateSimpleperfProcess(const std::string& simpleperf_path,
412 const std::vector<std::string>& record_args) {
413 // 1. Create control/reply pips.
414 int control_fd[2];
415 int reply_fd[2];
416 if (pipe(control_fd) != 0 || pipe(reply_fd) != 0) {
417 Abort("failed to call pipe: %s", strerror(errno));
418 }
419
420 // 2. Prepare simpleperf arguments.
421 std::vector<std::string> args;
422 args.emplace_back(simpleperf_path);
423 args.emplace_back("record");
424 args.emplace_back("--log-to-android-buffer");
425 args.insert(args.end(), {"--log", "debug"});
426 args.emplace_back("--stdio-controls-profiling");
427 args.emplace_back("--in-app");
428 args.insert(args.end(), {"--tracepoint-events", "/data/local/tmp/tracepoint_events"});
429 args.insert(args.end(), record_args.begin(), record_args.end());
430 char* argv[args.size() + 1];
431 for (size_t i = 0; i < args.size(); ++i) {
432 argv[i] = &args[i][0];
433 }
434 argv[args.size()] = nullptr;
435
436 // 3. Start simpleperf process.
437 // Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
438 // So we use vfork instead of fork to avoid calling them.
439 int pid = vfork();
440 if (pid == -1) {
441 Abort("failed to fork: %s", strerror(errno));
442 }
443 if (pid == 0) {
444 // child process
445 close(control_fd[1]);
446 dup2(control_fd[0], 0); // simpleperf read control cmd from fd 0.
447 close(control_fd[0]);
448 close(reply_fd[0]);
449 dup2(reply_fd[1], 1); // simpleperf writes reply to fd 1.
450 close(reply_fd[0]);
451 chdir(simpleperf_data_dir_.c_str());
452 execvp(argv[0], argv);
453 Abort("failed to call exec: %s", strerror(errno));
454 }
455 // parent process
456 close(control_fd[0]);
457 control_fd_ = control_fd[1];
458 close(reply_fd[1]);
459 reply_fd_ = reply_fd[0];
460 simpleperf_pid_ = pid;
461
462 // 4. Wait until simpleperf starts recording.
463 std::string start_flag = ReadReply();
464 if (start_flag != "started") {
465 Abort("failed to receive simpleperf start flag");
466 }
467 }
468
ReadReply()469 std::string ProfileSessionImpl::ReadReply() {
470 std::string s;
471 while (true) {
472 char c;
473 ssize_t result = TEMP_FAILURE_RETRY(read(reply_fd_, &c, 1));
474 if (result <= 0 || c == '\n') {
475 break;
476 }
477 s.push_back(c);
478 }
479 return s;
480 }
481
ProfileSession()482 ProfileSession::ProfileSession() {
483 FILE* fp = fopen("/proc/self/cmdline", "r");
484 if (fp == nullptr) {
485 Abort("failed to open /proc/self/cmdline: %s", strerror(errno));
486 }
487 std::string s = ReadFile(fp);
488 for (int i = 0; i < s.size(); i++) {
489 if (s[i] == '\0') {
490 s = s.substr(0, i);
491 break;
492 }
493 }
494 std::string app_data_dir = "/data/data/" + s;
495 int uid = getuid();
496 if (uid >= AID_USER_OFFSET) {
497 int user_id = uid / AID_USER_OFFSET;
498 app_data_dir = "/data/user/" + std::to_string(user_id) + "/" + s;
499 }
500 impl_ = new ProfileSessionImpl(app_data_dir);
501 }
502
ProfileSession(const std::string & app_data_dir)503 ProfileSession::ProfileSession(const std::string& app_data_dir)
504 : impl_(new ProfileSessionImpl(app_data_dir)) {}
505
~ProfileSession()506 ProfileSession::~ProfileSession() {
507 delete impl_;
508 }
509
StartRecording(const RecordOptions & options)510 void ProfileSession::StartRecording(const RecordOptions& options) {
511 StartRecording(options.ToRecordArgs());
512 }
513
StartRecording(const std::vector<std::string> & record_args)514 void ProfileSession::StartRecording(const std::vector<std::string>& record_args) {
515 impl_->StartRecording(record_args);
516 }
517
PauseRecording()518 void ProfileSession::PauseRecording() {
519 impl_->PauseRecording();
520 }
521
ResumeRecording()522 void ProfileSession::ResumeRecording() {
523 impl_->ResumeRecording();
524 }
525
StopRecording()526 void ProfileSession::StopRecording() {
527 impl_->StopRecording();
528 }
529
530 } // namespace simpleperf
531