| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <libgen.h> |
| #include <poll.h> |
| #include <signal.h> |
| #include <sys/utsname.h> |
| #include <unistd.h> |
| #include <set> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| #include <android-base/strings.h> |
| |
| #include "command.h" |
| #include "dwarf_unwind.h" |
| #include "environment.h" |
| #include "event_selection_set.h" |
| #include "event_type.h" |
| #include "read_apk.h" |
| #include "read_elf.h" |
| #include "record.h" |
| #include "record_file.h" |
| #include "scoped_signal_handler.h" |
| #include "thread_tree.h" |
| #include "utils.h" |
| #include "workload.h" |
| |
| static std::string default_measured_event_type = "cpu-cycles"; |
| |
| static std::unordered_map<std::string, uint64_t> branch_sampling_type_map = { |
| {"u", PERF_SAMPLE_BRANCH_USER}, |
| {"k", PERF_SAMPLE_BRANCH_KERNEL}, |
| {"any", PERF_SAMPLE_BRANCH_ANY}, |
| {"any_call", PERF_SAMPLE_BRANCH_ANY_CALL}, |
| {"any_ret", PERF_SAMPLE_BRANCH_ANY_RETURN}, |
| {"ind_call", PERF_SAMPLE_BRANCH_IND_CALL}, |
| }; |
| |
| static volatile bool signaled; |
| static void signal_handler(int) { |
| signaled = true; |
| } |
| |
| class RecordCommand : public Command { |
| public: |
| RecordCommand() |
| : Command( |
| "record", "record sampling info in perf.data", |
| "Usage: simpleperf record [options] [command [command-args]]\n" |
| " Gather sampling information when running [command].\n" |
| " -a System-wide collection.\n" |
| " -b Enable take branch stack sampling. Same as '-j any'\n" |
| " -c count Set event sample period.\n" |
| " --call-graph fp | dwarf[,<dump_stack_size>]\n" |
| " Enable call graph recording. Use frame pointer or dwarf as the\n" |
| " method to parse call graph in stack. Default is dwarf,8192.\n" |
| " --cpu cpu_item1,cpu_item2,...\n" |
| " Collect samples only on the selected cpus. cpu_item can be cpu\n" |
| " number like 1, or cpu range like 0-3.\n" |
| " -e event1[:modifier1],event2[:modifier2],...\n" |
| " Select the event list to sample. Use `simpleperf list` to find\n" |
| " all possible event names. Modifiers can be added to define\n" |
| " how the event should be monitored. Possible modifiers are:\n" |
| " u - monitor user space events only\n" |
| " k - monitor kernel space events only\n" |
| " -f freq Set event sample frequency.\n" |
| " -F freq Same as '-f freq'.\n" |
| " -g Same as '--call-graph dwarf'.\n" |
| " -j branch_filter1,branch_filter2,...\n" |
| " Enable taken branch stack sampling. Each sample\n" |
| " captures a series of consecutive taken branches.\n" |
| " The following filters are defined:\n" |
| " any: any type of branch\n" |
| " any_call: any function call or system call\n" |
| " any_ret: any function return or system call return\n" |
| " ind_call: any indirect branch\n" |
| " u: only when the branch target is at the user level\n" |
| " k: only when the branch target is in the kernel\n" |
| " This option requires at least one branch type among any,\n" |
| " any_call, any_ret, ind_call.\n" |
| " -m mmap_pages\n" |
| " Set the size of the buffer used to receiving sample data from\n" |
| " the kernel. It should be a power of 2. The default value is 16.\n" |
| " --no-inherit\n" |
| " Don't record created child threads/processes.\n" |
| " --no-unwind If `--call-graph dwarf` option is used, then the user's stack will\n" |
| " be unwound by default. Use this option to disable the unwinding of\n" |
| " the user's stack.\n" |
| " -o record_file_name Set record file name, default is perf.data.\n" |
| " -p pid1,pid2,...\n" |
| " Record events on existing processes. Mutually exclusive with -a.\n" |
| " --post-unwind\n" |
| " If `--call-graph dwarf` option is used, then the user's stack will\n" |
| " be unwound while recording by default. But it may lose records as\n" |
| " stacking unwinding can be time consuming. Use this option to unwind\n" |
| " the user's stack after recording.\n" |
| " -t tid1,tid2,...\n" |
| " Record events on existing threads. Mutually exclusive with -a.\n"), |
| use_sample_freq_(true), |
| sample_freq_(4000), |
| system_wide_collection_(false), |
| branch_sampling_(0), |
| fp_callchain_sampling_(false), |
| dwarf_callchain_sampling_(false), |
| dump_stack_size_in_dwarf_sampling_(8192), |
| unwind_dwarf_callchain_(true), |
| post_unwind_(false), |
| child_inherit_(true), |
| perf_mmap_pages_(16), |
| record_filename_("perf.data"), |
| sample_record_count_(0) { |
| signaled = false; |
| scoped_signal_handler_.reset( |
| new ScopedSignalHandler({SIGCHLD, SIGINT, SIGTERM}, signal_handler)); |
| } |
| |
| bool Run(const std::vector<std::string>& args); |
| |
| private: |
| bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args); |
| bool AddMeasuredEventType(const std::string& event_type_name); |
| bool SetEventSelection(); |
| bool CreateAndInitRecordFile(); |
| std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename); |
| bool DumpKernelAndModuleMmaps(const perf_event_attr* attr, uint64_t event_id); |
| bool DumpThreadCommAndMmaps(const perf_event_attr* attr, uint64_t event_id, |
| bool all_threads, const std::vector<pid_t>& selected_threads); |
| bool ProcessRecord(Record* record); |
| void UpdateRecordForEmbeddedElfPath(Record* record); |
| void UnwindRecord(Record* record); |
| bool PostUnwind(const std::vector<std::string>& args); |
| bool DumpAdditionalFeatures(const std::vector<std::string>& args); |
| bool DumpBuildIdFeature(); |
| void CollectHitFileInfo(Record* record); |
| std::pair<std::string, uint64_t> TestForEmbeddedElf(Dso *dso, uint64_t pgoff); |
| |
| bool use_sample_freq_; // Use sample_freq_ when true, otherwise using sample_period_. |
| uint64_t sample_freq_; // Sample 'sample_freq_' times per second. |
| uint64_t sample_period_; // Sample once when 'sample_period_' events occur. |
| |
| bool system_wide_collection_; |
| uint64_t branch_sampling_; |
| bool fp_callchain_sampling_; |
| bool dwarf_callchain_sampling_; |
| uint32_t dump_stack_size_in_dwarf_sampling_; |
| bool unwind_dwarf_callchain_; |
| bool post_unwind_; |
| bool child_inherit_; |
| std::vector<pid_t> monitored_threads_; |
| std::vector<int> cpus_; |
| std::vector<EventTypeAndModifier> measured_event_types_; |
| EventSelectionSet event_selection_set_; |
| |
| // mmap pages used by each perf event file, should be a power of 2. |
| size_t perf_mmap_pages_; |
| |
| ThreadTree thread_tree_; |
| std::string record_filename_; |
| std::unique_ptr<RecordFileWriter> record_file_writer_; |
| |
| std::set<std::string> hit_kernel_modules_; |
| std::set<std::string> hit_user_files_; |
| |
| std::unique_ptr<ScopedSignalHandler> scoped_signal_handler_; |
| uint64_t sample_record_count_; |
| }; |
| |
| bool RecordCommand::Run(const std::vector<std::string>& args) { |
| // 1. Parse options, and use default measured event type if not given. |
| std::vector<std::string> workload_args; |
| if (!ParseOptions(args, &workload_args)) { |
| return false; |
| } |
| if (measured_event_types_.empty()) { |
| if (!AddMeasuredEventType(default_measured_event_type)) { |
| return false; |
| } |
| } |
| if (!SetEventSelection()) { |
| return false; |
| } |
| |
| // 2. Create workload. |
| std::unique_ptr<Workload> workload; |
| if (!workload_args.empty()) { |
| workload = Workload::CreateWorkload(workload_args); |
| if (workload == nullptr) { |
| return false; |
| } |
| } |
| if (!system_wide_collection_ && monitored_threads_.empty()) { |
| if (workload != nullptr) { |
| monitored_threads_.push_back(workload->GetPid()); |
| event_selection_set_.SetEnableOnExec(true); |
| } else { |
| LOG(ERROR) << "No threads to monitor. Try `simpleperf help record` for help\n"; |
| return false; |
| } |
| } |
| |
| // 3. Open perf_event_files, create memory mapped buffers for perf_event_files, add prepare poll |
| // for perf_event_files. |
| if (system_wide_collection_) { |
| if (!event_selection_set_.OpenEventFilesForCpus(cpus_)) { |
| return false; |
| } |
| } else { |
| if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, cpus_)) { |
| return false; |
| } |
| } |
| if (!event_selection_set_.MmapEventFiles(perf_mmap_pages_)) { |
| return false; |
| } |
| std::vector<pollfd> pollfds; |
| event_selection_set_.PrepareToPollForEventFiles(&pollfds); |
| |
| // 4. Create perf.data. |
| if (!CreateAndInitRecordFile()) { |
| return false; |
| } |
| |
| // 5. Write records in mmap buffers of perf_event_files to output file while workload is running. |
| if (workload != nullptr && !workload->Start()) { |
| return false; |
| } |
| auto callback = std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1); |
| event_selection_set_.PrepareToReadMmapEventData(callback); |
| while (true) { |
| if (!event_selection_set_.ReadMmapEventData()) { |
| return false; |
| } |
| if (signaled) { |
| break; |
| } |
| poll(&pollfds[0], pollfds.size(), -1); |
| } |
| event_selection_set_.FinishReadMmapEventData(); |
| |
| // 6. Dump additional features, and close record file. |
| if (!DumpAdditionalFeatures(args)) { |
| return false; |
| } |
| if (!record_file_writer_->Close()) { |
| return false; |
| } |
| |
| // 7. Unwind dwarf callchain. |
| if (post_unwind_) { |
| if (!PostUnwind(args)) { |
| return false; |
| } |
| } |
| LOG(VERBOSE) << "Record " << sample_record_count_ << " samples."; |
| return true; |
| } |
| |
| bool RecordCommand::ParseOptions(const std::vector<std::string>& args, |
| std::vector<std::string>* non_option_args) { |
| std::set<pid_t> tid_set; |
| size_t i; |
| for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) { |
| if (args[i] == "-a") { |
| system_wide_collection_ = true; |
| } else if (args[i] == "-b") { |
| branch_sampling_ = branch_sampling_type_map["any"]; |
| } else if (args[i] == "-c") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| char* endptr; |
| sample_period_ = strtoull(args[i].c_str(), &endptr, 0); |
| if (*endptr != '\0' || sample_period_ == 0) { |
| LOG(ERROR) << "Invalid sample period: '" << args[i] << "'"; |
| return false; |
| } |
| use_sample_freq_ = false; |
| } else if (args[i] == "--call-graph") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| std::vector<std::string> strs = android::base::Split(args[i], ","); |
| if (strs[0] == "fp") { |
| fp_callchain_sampling_ = true; |
| dwarf_callchain_sampling_ = false; |
| } else if (strs[0] == "dwarf") { |
| fp_callchain_sampling_ = false; |
| dwarf_callchain_sampling_ = true; |
| if (strs.size() > 1) { |
| char* endptr; |
| uint64_t size = strtoull(strs[1].c_str(), &endptr, 0); |
| if (*endptr != '\0' || size > UINT_MAX) { |
| LOG(ERROR) << "invalid dump stack size in --call-graph option: " << strs[1]; |
| return false; |
| } |
| if ((size & 7) != 0) { |
| LOG(ERROR) << "dump stack size " << size << " is not 8-byte aligned."; |
| return false; |
| } |
| dump_stack_size_in_dwarf_sampling_ = static_cast<uint32_t>(size); |
| } |
| } else { |
| LOG(ERROR) << "unexpected argument for --call-graph option: " << args[i]; |
| return false; |
| } |
| } else if (args[i] == "--cpu") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| cpus_ = GetCpusFromString(args[i]); |
| } else if (args[i] == "-e") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| std::vector<std::string> event_types = android::base::Split(args[i], ","); |
| for (auto& event_type : event_types) { |
| if (!AddMeasuredEventType(event_type)) { |
| return false; |
| } |
| } |
| } else if (args[i] == "-f" || args[i] == "-F") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| char* endptr; |
| sample_freq_ = strtoull(args[i].c_str(), &endptr, 0); |
| if (*endptr != '\0' || sample_freq_ == 0) { |
| LOG(ERROR) << "Invalid sample frequency: '" << args[i] << "'"; |
| return false; |
| } |
| use_sample_freq_ = true; |
| } else if (args[i] == "-g") { |
| fp_callchain_sampling_ = false; |
| dwarf_callchain_sampling_ = true; |
| } else if (args[i] == "-j") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| std::vector<std::string> branch_sampling_types = android::base::Split(args[i], ","); |
| for (auto& type : branch_sampling_types) { |
| auto it = branch_sampling_type_map.find(type); |
| if (it == branch_sampling_type_map.end()) { |
| LOG(ERROR) << "unrecognized branch sampling filter: " << type; |
| return false; |
| } |
| branch_sampling_ |= it->second; |
| } |
| } else if (args[i] == "-m") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| char* endptr; |
| uint64_t pages = strtoull(args[i].c_str(), &endptr, 0); |
| if (*endptr != '\0' || !IsPowerOfTwo(pages)) { |
| LOG(ERROR) << "Invalid mmap_pages: '" << args[i] << "'"; |
| return false; |
| } |
| perf_mmap_pages_ = pages; |
| } else if (args[i] == "--no-inherit") { |
| child_inherit_ = false; |
| } else if (args[i] == "--no-unwind") { |
| unwind_dwarf_callchain_ = false; |
| } else if (args[i] == "-o") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| record_filename_ = args[i]; |
| } else if (args[i] == "-p") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| if (!GetValidThreadsFromProcessString(args[i], &tid_set)) { |
| return false; |
| } |
| } else if (args[i] == "--post-unwind") { |
| post_unwind_ = true; |
| } else if (args[i] == "-t") { |
| if (!NextArgumentOrError(args, &i)) { |
| return false; |
| } |
| if (!GetValidThreadsFromThreadString(args[i], &tid_set)) { |
| return false; |
| } |
| } else { |
| ReportUnknownOption(args, i); |
| return false; |
| } |
| } |
| |
| if (!dwarf_callchain_sampling_) { |
| if (!unwind_dwarf_callchain_) { |
| LOG(ERROR) << "--no-unwind is only used with `--call-graph dwarf` option."; |
| return false; |
| } |
| unwind_dwarf_callchain_ = false; |
| } |
| if (post_unwind_) { |
| if (!dwarf_callchain_sampling_) { |
| LOG(ERROR) << "--post-unwind is only used with `--call-graph dwarf` option."; |
| return false; |
| } |
| if (!unwind_dwarf_callchain_) { |
| LOG(ERROR) << "--post-unwind can't be used with `--no-unwind` option."; |
| return false; |
| } |
| } |
| |
| monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end()); |
| if (system_wide_collection_ && !monitored_threads_.empty()) { |
| LOG(ERROR) |
| << "Record system wide and existing processes/threads can't be used at the same time."; |
| return false; |
| } |
| |
| if (non_option_args != nullptr) { |
| non_option_args->clear(); |
| for (; i < args.size(); ++i) { |
| non_option_args->push_back(args[i]); |
| } |
| } |
| return true; |
| } |
| |
| bool RecordCommand::AddMeasuredEventType(const std::string& event_type_name) { |
| std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_name); |
| if (event_type_modifier == nullptr) { |
| return false; |
| } |
| measured_event_types_.push_back(*event_type_modifier); |
| return true; |
| } |
| |
| bool RecordCommand::SetEventSelection() { |
| for (auto& event_type : measured_event_types_) { |
| if (!event_selection_set_.AddEventType(event_type)) { |
| return false; |
| } |
| } |
| if (use_sample_freq_) { |
| event_selection_set_.SetSampleFreq(sample_freq_); |
| } else { |
| event_selection_set_.SetSamplePeriod(sample_period_); |
| } |
| event_selection_set_.SampleIdAll(); |
| if (!event_selection_set_.SetBranchSampling(branch_sampling_)) { |
| return false; |
| } |
| if (fp_callchain_sampling_) { |
| event_selection_set_.EnableFpCallChainSampling(); |
| } else if (dwarf_callchain_sampling_) { |
| if (!event_selection_set_.EnableDwarfCallChainSampling(dump_stack_size_in_dwarf_sampling_)) { |
| return false; |
| } |
| } |
| event_selection_set_.SetInherit(child_inherit_); |
| return true; |
| } |
| |
| bool RecordCommand::CreateAndInitRecordFile() { |
| record_file_writer_ = CreateRecordFile(record_filename_); |
| if (record_file_writer_ == nullptr) { |
| return false; |
| } |
| // Use first perf_event_attr and first event id to dump mmap and comm records. |
| const perf_event_attr* attr = event_selection_set_.FindEventAttrByType(measured_event_types_[0]); |
| const std::vector<std::unique_ptr<EventFd>>* fds = |
| event_selection_set_.FindEventFdsByType(measured_event_types_[0]); |
| uint64_t event_id = (*fds)[0]->Id(); |
| if (!DumpKernelAndModuleMmaps(attr, event_id)) { |
| return false; |
| } |
| if (!DumpThreadCommAndMmaps(attr, event_id, system_wide_collection_, monitored_threads_)) { |
| return false; |
| } |
| return true; |
| } |
| |
| std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename) { |
| std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename); |
| if (writer == nullptr) { |
| return nullptr; |
| } |
| |
| std::vector<AttrWithId> attr_ids; |
| for (auto& event_type : measured_event_types_) { |
| AttrWithId attr_id; |
| attr_id.attr = event_selection_set_.FindEventAttrByType(event_type); |
| CHECK(attr_id.attr != nullptr); |
| const std::vector<std::unique_ptr<EventFd>>* fds = |
| event_selection_set_.FindEventFdsByType(event_type); |
| CHECK(fds != nullptr); |
| for (auto& fd : *fds) { |
| attr_id.ids.push_back(fd->Id()); |
| } |
| attr_ids.push_back(attr_id); |
| } |
| if (!writer->WriteAttrSection(attr_ids)) { |
| return nullptr; |
| } |
| return writer; |
| } |
| |
| bool RecordCommand::DumpKernelAndModuleMmaps(const perf_event_attr* attr, uint64_t event_id) { |
| KernelMmap kernel_mmap; |
| std::vector<KernelMmap> module_mmaps; |
| GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps); |
| |
| MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, kernel_mmap.start_addr, |
| kernel_mmap.len, 0, kernel_mmap.filepath, event_id); |
| if (!ProcessRecord(&mmap_record)) { |
| return false; |
| } |
| for (auto& module_mmap : module_mmaps) { |
| MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, module_mmap.start_addr, |
| module_mmap.len, 0, module_mmap.filepath, event_id); |
| if (!ProcessRecord(&mmap_record)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr* attr, uint64_t event_id, |
| bool all_threads, |
| const std::vector<pid_t>& selected_threads) { |
| std::vector<ThreadComm> thread_comms; |
| if (!GetThreadComms(&thread_comms)) { |
| return false; |
| } |
| // Decide which processes and threads to dump. |
| std::set<pid_t> dump_processes; |
| std::set<pid_t> dump_threads; |
| for (auto& tid : selected_threads) { |
| dump_threads.insert(tid); |
| } |
| for (auto& thread : thread_comms) { |
| if (dump_threads.find(thread.tid) != dump_threads.end()) { |
| dump_processes.insert(thread.pid); |
| } |
| } |
| |
| // Dump processes. |
| for (auto& thread : thread_comms) { |
| if (thread.pid != thread.tid) { |
| continue; |
| } |
| if (!all_threads && dump_processes.find(thread.pid) == dump_processes.end()) { |
| continue; |
| } |
| CommRecord record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm, event_id); |
| if (!ProcessRecord(&record)) { |
| return false; |
| } |
| std::vector<ThreadMmap> thread_mmaps; |
| if (!GetThreadMmapsInProcess(thread.pid, &thread_mmaps)) { |
| // The thread may exit before we get its info. |
| continue; |
| } |
| for (auto& thread_mmap : thread_mmaps) { |
| if (thread_mmap.executable == 0) { |
| continue; // No need to dump non-executable mmap info. |
| } |
| MmapRecord record = |
| CreateMmapRecord(*attr, false, thread.pid, thread.tid, thread_mmap.start_addr, |
| thread_mmap.len, thread_mmap.pgoff, thread_mmap.name, event_id); |
| if (!ProcessRecord(&record)) { |
| return false; |
| } |
| } |
| } |
| |
| // Dump threads. |
| for (auto& thread : thread_comms) { |
| if (thread.pid == thread.tid) { |
| continue; |
| } |
| if (!all_threads && dump_threads.find(thread.tid) == dump_threads.end()) { |
| continue; |
| } |
| ForkRecord fork_record = CreateForkRecord(*attr, thread.pid, thread.tid, thread.pid, |
| thread.pid, event_id); |
| if (!ProcessRecord(&fork_record)) { |
| return false; |
| } |
| CommRecord comm_record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm, |
| event_id); |
| if (!ProcessRecord(&comm_record)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool RecordCommand::ProcessRecord(Record* record) { |
| UpdateRecordForEmbeddedElfPath(record); |
| BuildThreadTree(*record, &thread_tree_); |
| CollectHitFileInfo(record); |
| if (unwind_dwarf_callchain_ && !post_unwind_) { |
| UnwindRecord(record); |
| } |
| if (record->type() == PERF_RECORD_SAMPLE) { |
| sample_record_count_++; |
| } |
| bool result = record_file_writer_->WriteData(record->BinaryFormat()); |
| return result; |
| } |
| |
| template<class RecordType> |
| void UpdateMmapRecordForEmbeddedElfPath(RecordType* record) { |
| RecordType& r = *record; |
| bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL); |
| if (!in_kernel && r.data.pgoff != 0) { |
| // For the case of a shared library "foobar.so" embedded |
| // inside an APK, we rewrite the original MMAP from |
| // ["path.apk" offset=X] to ["path.apk!/foobar.so" offset=W] |
| // so as to make the library name explicit. This update is |
| // done here (as part of the record operation) as opposed to |
| // on the host during the report, since we want to report |
| // the correct library name even if the the APK in question |
| // is not present on the host. The new offset W is |
| // calculated to be with respect to the start of foobar.so, |
| // not to the start of path.apk. |
| EmbeddedElf* ee = ApkInspector::FindElfInApkByOffset(r.filename, r.data.pgoff); |
| if (ee != nullptr) { |
| // Compute new offset relative to start of elf in APK. |
| r.data.pgoff -= ee->entry_offset(); |
| r.filename = GetUrlInApk(r.filename, ee->entry_name()); |
| r.AdjustSizeBasedOnData(); |
| } |
| } |
| } |
| |
| void RecordCommand::UpdateRecordForEmbeddedElfPath(Record* record) { |
| if (record->type() == PERF_RECORD_MMAP) { |
| UpdateMmapRecordForEmbeddedElfPath(static_cast<MmapRecord*>(record)); |
| } else if (record->type() == PERF_RECORD_MMAP2) { |
| UpdateMmapRecordForEmbeddedElfPath(static_cast<Mmap2Record*>(record)); |
| } |
| } |
| |
| void RecordCommand::UnwindRecord(Record* record) { |
| if (record->type() == PERF_RECORD_SAMPLE) { |
| SampleRecord& r = *static_cast<SampleRecord*>(record); |
| if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) && |
| (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) && |
| (!r.stack_user_data.data.empty())) { |
| ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); |
| RegSet regs = CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs); |
| std::vector<char>& stack = r.stack_user_data.data; |
| ArchType arch = GetArchForAbi(GetBuildArch(), r.regs_user_data.abi); |
| // Normally do strict arch check when unwinding stack. But allow unwinding 32-bit processes |
| // on 64-bit devices for system wide profiling. |
| bool strict_arch_check = !system_wide_collection_; |
| std::vector<uint64_t> unwind_ips = UnwindCallChain(arch, *thread, regs, stack, |
| strict_arch_check); |
| r.callchain_data.ips.push_back(PERF_CONTEXT_USER); |
| r.callchain_data.ips.insert(r.callchain_data.ips.end(), unwind_ips.begin(), unwind_ips.end()); |
| r.regs_user_data.abi = 0; |
| r.regs_user_data.reg_mask = 0; |
| r.regs_user_data.regs.clear(); |
| r.stack_user_data.data.clear(); |
| r.stack_user_data.dyn_size = 0; |
| r.AdjustSizeBasedOnData(); |
| } |
| } |
| } |
| |
| bool RecordCommand::PostUnwind(const std::vector<std::string>& args) { |
| thread_tree_.Clear(); |
| std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_filename_); |
| if (reader == nullptr) { |
| return false; |
| } |
| std::string tmp_filename = record_filename_ + ".tmp"; |
| record_file_writer_ = CreateRecordFile(tmp_filename); |
| if (record_file_writer_ == nullptr) { |
| return false; |
| } |
| bool result = reader->ReadDataSection( |
| [this](std::unique_ptr<Record> record) { |
| BuildThreadTree(*record, &thread_tree_); |
| UnwindRecord(record.get()); |
| return record_file_writer_->WriteData(record->BinaryFormat()); |
| }, |
| false); |
| if (!result) { |
| return false; |
| } |
| if (!DumpAdditionalFeatures(args)) { |
| return false; |
| } |
| if (!record_file_writer_->Close()) { |
| return false; |
| } |
| |
| if (unlink(record_filename_.c_str()) != 0) { |
| PLOG(ERROR) << "failed to remove " << record_filename_; |
| return false; |
| } |
| if (rename(tmp_filename.c_str(), record_filename_.c_str()) != 0) { |
| PLOG(ERROR) << "failed to rename " << tmp_filename << " to " << record_filename_; |
| return false; |
| } |
| return true; |
| } |
| |
| bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args) { |
| size_t feature_count = (branch_sampling_ != 0 ? 5 : 4); |
| if (!record_file_writer_->WriteFeatureHeader(feature_count)) { |
| return false; |
| } |
| if (!DumpBuildIdFeature()) { |
| return false; |
| } |
| utsname uname_buf; |
| if (TEMP_FAILURE_RETRY(uname(&uname_buf)) != 0) { |
| PLOG(ERROR) << "uname() failed"; |
| return false; |
| } |
| if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE, uname_buf.release)) { |
| return false; |
| } |
| if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH, uname_buf.machine)) { |
| return false; |
| } |
| |
| std::string exec_path = "simpleperf"; |
| GetExecPath(&exec_path); |
| std::vector<std::string> cmdline; |
| cmdline.push_back(exec_path); |
| cmdline.push_back("record"); |
| cmdline.insert(cmdline.end(), args.begin(), args.end()); |
| if (!record_file_writer_->WriteCmdlineFeature(cmdline)) { |
| return false; |
| } |
| if (branch_sampling_ != 0 && !record_file_writer_->WriteBranchStackFeature()) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool RecordCommand::DumpBuildIdFeature() { |
| std::vector<BuildIdRecord> build_id_records; |
| BuildId build_id; |
| // Add build_ids for kernel/modules. |
| for (const auto& filename : hit_kernel_modules_) { |
| if (filename == DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID) { |
| if (!GetKernelBuildId(&build_id)) { |
| LOG(DEBUG) << "can't read build_id for kernel"; |
| continue; |
| } |
| build_id_records.push_back( |
| CreateBuildIdRecord(true, UINT_MAX, build_id, DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID)); |
| } else { |
| std::string path = filename; |
| std::string module_name = basename(&path[0]); |
| if (android::base::EndsWith(module_name, ".ko")) { |
| module_name = module_name.substr(0, module_name.size() - 3); |
| } |
| if (!GetModuleBuildId(module_name, &build_id)) { |
| LOG(DEBUG) << "can't read build_id for module " << module_name; |
| continue; |
| } |
| build_id_records.push_back(CreateBuildIdRecord(true, UINT_MAX, build_id, filename)); |
| } |
| } |
| // Add build_ids for user elf files. |
| for (const auto& filename : hit_user_files_) { |
| if (filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) { |
| continue; |
| } |
| auto tuple = SplitUrlInApk(filename); |
| if (std::get<0>(tuple)) { |
| if (!GetBuildIdFromApkFile(std::get<1>(tuple), std::get<2>(tuple), &build_id)) { |
| LOG(DEBUG) << "can't read build_id from file " << filename; |
| continue; |
| } |
| } else { |
| if (!GetBuildIdFromElfFile(filename, &build_id)) { |
| LOG(DEBUG) << "can't read build_id from file " << filename; |
| continue; |
| } |
| } |
| build_id_records.push_back(CreateBuildIdRecord(false, UINT_MAX, build_id, filename)); |
| } |
| if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) { |
| return false; |
| } |
| return true; |
| } |
| |
| void RecordCommand::CollectHitFileInfo(Record* record) { |
| if (record->type() == PERF_RECORD_SAMPLE) { |
| auto r = *static_cast<SampleRecord*>(record); |
| bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL); |
| const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); |
| const MapEntry* map = thread_tree_.FindMap(thread, r.ip_data.ip, in_kernel); |
| if (in_kernel) { |
| hit_kernel_modules_.insert(map->dso->Path()); |
| } else { |
| hit_user_files_.insert(map->dso->Path()); |
| } |
| } |
| } |
| |
| void RegisterRecordCommand() { |
| RegisterCommand("record", [] { return std::unique_ptr<Command>(new RecordCommand()); }); |
| } |