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