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 52 RecordOptions::RecordOptions() : impl_(new RecordOptionsImpl) { 53 } 54 55 RecordOptions::~RecordOptions() { 56 delete impl_; 57 } 58 59 RecordOptions& RecordOptions::SetOutputFilename(const std::string &filename) { 60 impl_->output_filename = filename; 61 return *this; 62 } 63 64 RecordOptions& RecordOptions::SetEvent(const std::string &event) { 65 impl_->event = event; 66 return *this; 67 } 68 69 RecordOptions& RecordOptions::SetSampleFrequency(size_t freq) { 70 impl_->freq = freq; 71 return *this; 72 } 73 74 RecordOptions& RecordOptions::SetDuration(double duration_in_second) { 75 impl_->duration_in_second = duration_in_second; 76 return *this; 77 } 78 79 RecordOptions& RecordOptions::SetSampleThreads(const std::vector<pid_t> &threads) { 80 impl_->threads = threads; 81 return *this; 82 } 83 84 RecordOptions& RecordOptions::RecordDwarfCallGraph() { 85 impl_->dwarf_callgraph = true; 86 impl_->fp_callgraph = false; 87 return *this; 88 } 89 90 RecordOptions& RecordOptions::RecordFramePointerCallGraph() { 91 impl_->fp_callgraph = true; 92 impl_->dwarf_callgraph = false; 93 return *this; 94 } 95 96 RecordOptions& RecordOptions::TraceOffCpu() { 97 impl_->trace_offcpu = true; 98 return *this; 99 } 100 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 115 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 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: 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 bool trace_offcpu_ = false; 192 }; 193 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 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 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 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 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 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 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 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 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 337 std::string ProfileSessionImpl::FindSimpleperf() { 338 // 1. Try /data/local/tmp/simpleperf first. Probably it's newer than /system/bin/simpleperf. 339 std::string simpleperf_path = FindSimpleperfInTempDir(); 340 if (!simpleperf_path.empty()) { 341 return simpleperf_path; 342 } 343 // 2. Try /system/bin/simpleperf, which is available on Android >= Q. 344 simpleperf_path = "/system/bin/simpleperf"; 345 if (IsExecutableFile(simpleperf_path)) { 346 return simpleperf_path; 347 } 348 Abort("can't find simpleperf on device. Please run api_profiler.py."); 349 return ""; 350 } 351 352 std::string ProfileSessionImpl::FindSimpleperfInTempDir() { 353 const std::string path = "/data/local/tmp/simpleperf"; 354 if (!IsExecutableFile(path)) { 355 return ""; 356 } 357 // Copy it to app_dir to execute it. 358 const std::string to_path = app_data_dir_ + "/simpleperf"; 359 if (!RunCmd({"/system/bin/cp", path.c_str(), to_path.c_str()}, nullptr)) { 360 return ""; 361 } 362 // For apps with target sdk >= 29, executing app data file isn't allowed. 363 // For android R, app context isn't allowed to use perf_event_open. 364 // So test executing downloaded simpleperf. 365 std::string s; 366 if (!RunCmd({to_path.c_str(), "list", "sw"}, &s)) { 367 return ""; 368 } 369 if (s.find("cpu-clock") == std::string::npos) { 370 return ""; 371 } 372 return to_path; 373 } 374 375 void ProfileSessionImpl::CheckIfPerfEnabled() { 376 std::string s; 377 if (!RunCmd({"/system/bin/getprop", "security.perf_harden"}, &s)) { 378 return; // Omit check if getprop doesn't exist. 379 } 380 if (!s.empty() && s[0] == '1') { 381 Abort("linux perf events aren't enabled on the device. Please run api_profiler.py."); 382 } 383 } 384 385 void ProfileSessionImpl::CreateSimpleperfDataDir() { 386 struct stat st; 387 if (stat(simpleperf_data_dir_.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) { 388 return; 389 } 390 if (mkdir(simpleperf_data_dir_.c_str(), 0700) == -1) { 391 Abort("failed to create simpleperf data dir %s: %s", simpleperf_data_dir_.c_str(), 392 strerror(errno)); 393 } 394 } 395 396 void ProfileSessionImpl::CreateSimpleperfProcess(const std::string &simpleperf_path, 397 const std::vector<std::string> &record_args) { 398 // 1. Create control/reply pips. 399 int control_fd[2]; 400 int reply_fd[2]; 401 if (pipe(control_fd) != 0 || pipe(reply_fd) != 0) { 402 Abort("failed to call pipe: %s", strerror(errno)); 403 } 404 405 // 2. Prepare simpleperf arguments. 406 std::vector<std::string> args; 407 args.emplace_back(simpleperf_path); 408 args.emplace_back("record"); 409 args.emplace_back("--log-to-android-buffer"); 410 args.insert(args.end(), {"--log", "debug"}); 411 args.emplace_back("--stdio-controls-profiling"); 412 args.emplace_back("--in-app"); 413 args.insert(args.end(), {"--tracepoint-events", "/data/local/tmp/tracepoint_events"}); 414 args.insert(args.end(), record_args.begin(), record_args.end()); 415 char* argv[args.size() + 1]; 416 for (size_t i = 0; i < args.size(); ++i) { 417 argv[i] = &args[i][0]; 418 } 419 argv[args.size()] = nullptr; 420 421 // 3. Start simpleperf process. 422 // Fork handlers (like gsl_library_close) may hang in a multi-thread environment. 423 // So we use vfork instead of fork to avoid calling them. 424 int pid = vfork(); 425 if (pid == -1) { 426 Abort("failed to fork: %s", strerror(errno)); 427 } 428 if (pid == 0) { 429 // child process 430 close(control_fd[1]); 431 dup2(control_fd[0], 0); // simpleperf read control cmd from fd 0. 432 close(control_fd[0]); 433 close(reply_fd[0]); 434 dup2(reply_fd[1], 1); // simpleperf writes reply to fd 1. 435 close(reply_fd[0]); 436 chdir(simpleperf_data_dir_.c_str()); 437 execvp(argv[0], argv); 438 Abort("failed to call exec: %s", strerror(errno)); 439 } 440 // parent process 441 close(control_fd[0]); 442 control_fd_ = control_fd[1]; 443 close(reply_fd[1]); 444 reply_fd_ = reply_fd[0]; 445 simpleperf_pid_ = pid; 446 447 // 4. Wait until simpleperf starts recording. 448 std::string start_flag = ReadReply(); 449 if (start_flag != "started") { 450 Abort("failed to receive simpleperf start flag"); 451 } 452 } 453 454 std::string ProfileSessionImpl::ReadReply() { 455 std::string s; 456 while (true) { 457 char c; 458 ssize_t result = TEMP_FAILURE_RETRY(read(reply_fd_, &c, 1)); 459 if (result <= 0 || c == '\n') { 460 break; 461 } 462 s.push_back(c); 463 } 464 return s; 465 } 466 467 ProfileSession::ProfileSession() { 468 FILE* fp = fopen("/proc/self/cmdline", "r"); 469 if (fp == nullptr) { 470 Abort("failed to open /proc/self/cmdline: %s", strerror(errno)); 471 } 472 std::string s = ReadFile(fp); 473 for (int i = 0; i < s.size(); i++) { 474 if (s[i] == '\0') { 475 s = s.substr(0, i); 476 break; 477 } 478 } 479 std::string app_data_dir = "/data/data/" + s; 480 impl_ = new ProfileSessionImpl(app_data_dir); 481 } 482 483 ProfileSession::ProfileSession(const std::string& app_data_dir) 484 : impl_(new ProfileSessionImpl(app_data_dir)) {} 485 486 ProfileSession::~ProfileSession() { 487 delete impl_; 488 } 489 490 void ProfileSession::StartRecording(const RecordOptions &options) { 491 StartRecording(options.ToRecordArgs()); 492 } 493 494 void ProfileSession::StartRecording(const std::vector<std::string> &record_args) { 495 impl_->StartRecording(record_args); 496 } 497 498 void ProfileSession::PauseRecording() { 499 impl_->PauseRecording(); 500 } 501 502 void ProfileSession::ResumeRecording() { 503 impl_->ResumeRecording(); 504 } 505 506 void ProfileSession::StopRecording() { 507 impl_->StopRecording(); 508 } 509 510 } // namespace simpleperf 511