/* * Copyright (C) 2020 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__ANDROID__) #include #endif #include "IOEventLoop.h" #include "MapRecordReader.h" #include "OfflineUnwinder.h" #include "RecordFilter.h" #include "command.h" #include "dso.h" #include "environment.h" #include "event_selection_set.h" #include "event_type.h" #include "read_elf.h" #include "read_symbol_map.h" #include "record.h" #include "thread_tree.h" #include "tracing.h" #include "utils.h" namespace simpleperf { namespace { using android::base::ParseUint; using android::base::Realpath; using android::base::StringAppendF; struct SymbolInfo { Dso* dso; const Symbol* symbol; uint64_t vaddr_in_file; }; // The max size of records dumped by kernel is 65535, and dump stack size // should be a multiply of 8, so MAX_DUMP_STACK_SIZE is 65528. constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528; // The max allowed pages in mapped buffer is decided by rlimit(RLIMIT_MEMLOCK). // Here 1024 is a desired value for pages in mapped buffer. If mapped // successfully, the buffer size = 1024 * 4K (page size) = 4M. constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024; // Currently, the record buffer size in user-space is set to match the kernel // buffer size on a 8 core system. For system-wide recording, it is 8K pages * // 4K page_size * 8 cores = 256MB. For non system-wide recording, it is 1K pages // * 4K page_size * 8 cores = 64MB. static constexpr size_t kRecordBufferSize = 64 * kMegabyte; static constexpr size_t kSystemWideRecordBufferSize = 256 * kMegabyte; class MonitorCommand : public Command { public: MonitorCommand() : Command( "monitor", "monitor events and print their textual representations to stdout", // clang-format off "Usage: simpleperf monitor [options]\n" " Gather sampling information and print the events on stdout.\n" " For precise recording, prefer the record command.\n" " Currently, only supports system-wide collection.\n" "\n" "Select monitored threads:\n" "-a System-wide collection. Use with --exclude-perf to exclude\n" " samples for simpleperf process.\n" "\n" "Select monitored event types:\n" "-e event1[:modifier1],event2[:modifier2],...\n" " Select a list of events to record. An event can be:\n" " 1) an event name listed in `simpleperf list`;\n" " 2) a raw PMU event in rN format. N is a hex number.\n" " For example, r1b selects event number 0x1b.\n" " Modifiers can be added to define how the event should be\n" " monitored. Possible modifiers are:\n" " u - monitor user space events only\n" " k - monitor kernel space events only\n" "\n" "Select monitoring options:\n" "-f freq Set event sample frequency. It means recording at most [freq]\n" " samples every second. For non-tracepoint events, the default\n" " option is -f 4000. A -f/-c option affects all event types\n" " following it until meeting another -f/-c option. For example,\n" " for \"-f 1000 -e cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n" " has sample freq 1000, sched:sched_switch event has sample period 1.\n" "-c count Set event sample period. It means recording one sample when\n" " [count] events happen. For tracepoint events, the default option\n" " is -c 1.\n" "--call-graph fp | dwarf[,]\n" " Enable call graph recording. Use frame pointer or dwarf debug\n" " frame as the method to parse call graph in stack.\n" " Default is dwarf,65528.\n" "-g Same as '--call-graph dwarf'.\n" "--duration time_in_sec Monitor for time_in_sec seconds. Here time_in_sec" " may be any positive floating point number.\n" "--cpu-percent Set the max percent of cpu time used for recording.\n" " percent is in range [1-100], default is 25.\n" "\n" "Sample filter options:\n" "--exclude-perf Exclude samples for simpleperf process.\n" RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING "\n" // clang-format on ), system_wide_collection_(false), fp_callchain_sampling_(false), dwarf_callchain_sampling_(false), dump_stack_size_in_dwarf_sampling_(MAX_DUMP_STACK_SIZE), unwind_dwarf_callchain_(true), duration_in_sec_(0), event_selection_set_(false), mmap_page_range_(std::make_pair(1, DESIRED_PAGES_IN_MAPPED_BUFFER)), sample_record_count_(0), last_record_timestamp_(0u), record_filter_(thread_tree_) { // If we run `adb shell simpleperf record xxx` and stop profiling by ctrl-c, // adb closes sockets connecting simpleperf. After that, simpleperf will // receive SIGPIPE when writing to stdout/stderr, which is a problem when we // use '--app' option. So ignore SIGPIPE to finish properly. signal(SIGPIPE, SIG_IGN); } bool Run(const std::vector& args); private: bool ParseOptions(const std::vector& args); bool AdjustPerfEventLimit(); bool PrepareMonitoring(); bool DoMonitoring(); bool SetEventSelectionFlags(); bool DumpProcessMaps(pid_t pid, const std::unordered_set& tids); void DumpSampleRecord(const SampleRecord& sr); void DumpSampleCallchain(const SampleRecord& sr); bool ProcessRecord(Record* record); SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, bool in_kernel); bool DumpMapsForRecord(Record* record); void UpdateRecord(Record* record); bool UnwindRecord(SampleRecord& r); uint64_t max_sample_freq_ = DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT; size_t cpu_time_max_percent_ = 25; bool system_wide_collection_; bool fp_callchain_sampling_; bool dwarf_callchain_sampling_; uint32_t dump_stack_size_in_dwarf_sampling_; bool unwind_dwarf_callchain_; std::unique_ptr offline_unwinder_; double duration_in_sec_; EventSelectionSet event_selection_set_; std::pair mmap_page_range_; ThreadTree thread_tree_; uint64_t sample_record_count_; uint64_t last_record_timestamp_; // used to insert Mmap2Records for JIT debug info // In system wide recording, record if we have dumped map info for a process. std::unordered_set dumped_processes_; bool exclude_perf_ = false; RecordFilter record_filter_; std::unordered_map event_names_; std::optional map_record_reader_; }; bool MonitorCommand::Run(const std::vector& args) { ScopedCurrentArch scoped_arch(GetMachineArch()); if (!CheckPerfEventLimit()) { return false; } AllowMoreOpenedFiles(); if (!ParseOptions(args)) { return false; } if (!AdjustPerfEventLimit()) { return false; } if (!PrepareMonitoring()) { return false; } return DoMonitoring(); } bool MonitorCommand::PrepareMonitoring() { // 1. Process options before opening perf event files. if (!SetEventSelectionFlags()) { return false; } if (unwind_dwarf_callchain_) { offline_unwinder_ = OfflineUnwinder::Create(false); } // 2. Add monitored targets. if (system_wide_collection_) { event_selection_set_.AddMonitoredThreads({-1}); } else { LOG(ERROR) << "No threads to monitor. Try `simpleperf help monitor` for help"; return false; } // 3. Open perf event files and create mapped buffers. if (!event_selection_set_.OpenEventFiles()) { return false; } size_t record_buffer_size = system_wide_collection_ ? kSystemWideRecordBufferSize : kRecordBufferSize; if (!event_selection_set_.MmapEventFiles(mmap_page_range_.first, mmap_page_range_.second, 0 /* aux_buffer_size */, record_buffer_size, false /* allow_truncating_samples */, exclude_perf_)) { return false; } auto callback = std::bind(&MonitorCommand::ProcessRecord, this, std::placeholders::_1); if (!event_selection_set_.PrepareToReadMmapEventData(callback)) { return false; } // Keep track of the event names per id. event_names_ = event_selection_set_.GetEventNamesById(); // Use first perf_event_attr and first event id to dump mmap and comm records. EventAttrWithId dumping_attr_id = event_selection_set_.GetEventAttrWithId()[0]; map_record_reader_.emplace(dumping_attr_id.attr, dumping_attr_id.ids[0], event_selection_set_.RecordNotExecutableMaps()); map_record_reader_->SetCallback([this](Record* r) { return ProcessRecord(r); }); // 4. Load kallsyms, if possible. std::string kallsyms; if (LoadKernelSymbols(&kallsyms)) { Dso::SetKallsyms(std::move(kallsyms)); } map_record_reader_->ReadKernelMaps(); // 5. Add read/signal/periodic Events. IOEventLoop* loop = event_selection_set_.GetIOEventLoop(); auto exit_loop_callback = [loop]() { return loop->ExitLoop(); }; if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM}, exit_loop_callback, IOEventHighPriority)) { return false; } // Only add an event for SIGHUP if we didn't inherit SIG_IGN (e.g. from // nohup). if (!SignalIsIgnored(SIGHUP)) { if (!loop->AddSignalEvent(SIGHUP, exit_loop_callback, IOEventHighPriority)) { return false; } } if (duration_in_sec_ != 0) { if (!loop->AddPeriodicEvent( SecondToTimeval(duration_in_sec_), [loop]() { return loop->ExitLoop(); }, IOEventHighPriority)) { return false; } } return true; } bool MonitorCommand::DoMonitoring() { if (!event_selection_set_.GetIOEventLoop()->RunLoop()) { return false; } if (!event_selection_set_.SyncKernelBuffer()) { return false; } event_selection_set_.CloseEventFiles(); if (!event_selection_set_.FinishReadMmapEventData()) { return false; } LOG(ERROR) << "Processed samples: " << sample_record_count_; return true; } inline const OptionFormatMap& GetMonitorCmdOptionFormats() { static OptionFormatMap option_formats; if (option_formats.empty()) { option_formats = { {"-a", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}}, {"-c", {OptionValueType::UINT, OptionType::ORDERED, AppRunnerType::ALLOWED}}, {"--call-graph", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}}, {"--cpu-percent", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}}, {"--duration", {OptionValueType::DOUBLE, OptionType::SINGLE, AppRunnerType::ALLOWED}}, {"-e", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}}, {"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}}, {"-f", {OptionValueType::UINT, OptionType::ORDERED, AppRunnerType::ALLOWED}}, {"-g", {OptionValueType::NONE, OptionType::ORDERED, AppRunnerType::ALLOWED}}, {"-t", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}}, }; OptionFormatMap record_filter_options = GetRecordFilterOptionFormats(true); option_formats.insert(record_filter_options.begin(), record_filter_options.end()); } return option_formats; } bool MonitorCommand::ParseOptions(const std::vector& args) { OptionValueMap options; std::vector> ordered_options; if (!PreprocessOptions(args, GetMonitorCmdOptionFormats(), &options, &ordered_options, nullptr)) { return false; } // Process options. system_wide_collection_ = options.PullBoolValue("-a"); if (!options.PullUintValue("--cpu-percent", &cpu_time_max_percent_, 1, 100)) { return false; } if (!options.PullDoubleValue("--duration", &duration_in_sec_, 1e-9)) { return false; } exclude_perf_ = options.PullBoolValue("--exclude-perf"); if (!record_filter_.ParseOptions(options)) { return false; } CHECK(options.values.empty()); // Process ordered options. for (const auto& pair : ordered_options) { const OptionName& name = pair.first; const OptionValue& value = pair.second; if (name == "-c" || name == "-f") { if (value.uint_value < 1) { LOG(ERROR) << "invalid " << name << ": " << value.uint_value; return false; } SampleRate rate; if (name == "-c") { rate.sample_period = value.uint_value; } else { if (value.uint_value >= INT_MAX) { LOG(ERROR) << "sample freq can't be bigger than INT_MAX: " << value.uint_value; return false; } rate.sample_freq = value.uint_value; } event_selection_set_.SetSampleRateForNewEvents(rate); } else if (name == "--call-graph") { std::vector strs = android::base::Split(*value.str_value, ","); 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) { uint64_t size; if (!ParseUint(strs[1], &size)) { 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; } if (size >= MAX_DUMP_STACK_SIZE) { LOG(ERROR) << "dump stack size " << size << " is bigger than max allowed size " << MAX_DUMP_STACK_SIZE << "."; return false; } dump_stack_size_in_dwarf_sampling_ = static_cast(size); } } } else if (name == "-e") { std::vector event_types = android::base::Split(*value.str_value, ","); for (auto& event_type : event_types) { if (!event_selection_set_.AddEventType(event_type)) { return false; } } } else if (name == "-g") { fp_callchain_sampling_ = false; dwarf_callchain_sampling_ = true; } else { CHECK(false) << "unprocessed option: " << name; } } if (event_selection_set_.empty()) { LOG(ERROR) << "No event to record. Use `-e` to specify which event should be monitored."; return false; } if (fp_callchain_sampling_) { if (GetTargetArch() == ARCH_ARM) { LOG(WARNING) << "`--callgraph fp` option doesn't work well on arm architecture, " << "consider using `-g` option or profiling on aarch64 architecture."; } } if (system_wide_collection_ && event_selection_set_.HasMonitoredTarget()) { LOG(ERROR) << "Record system wide and existing processes/threads can't be " "used at the same time."; return false; } if (system_wide_collection_ && !IsRoot()) { LOG(ERROR) << "System wide profiling needs root privilege."; return false; } return true; } bool MonitorCommand::AdjustPerfEventLimit() { bool set_prop = false; // 1. Adjust max_sample_rate. uint64_t cur_max_freq; if (GetMaxSampleFrequency(&cur_max_freq) && cur_max_freq < max_sample_freq_ && !SetMaxSampleFrequency(max_sample_freq_)) { set_prop = true; } // 2. Adjust perf_cpu_time_max_percent. size_t cur_percent; if (GetCpuTimeMaxPercent(&cur_percent) && cur_percent != cpu_time_max_percent_ && !SetCpuTimeMaxPercent(cpu_time_max_percent_)) { set_prop = true; } // 3. Adjust perf_event_mlock_kb. long cpus = sysconf(_SC_NPROCESSORS_CONF); uint64_t mlock_kb = cpus * (mmap_page_range_.second + 1) * 4; uint64_t cur_mlock_kb; if (GetPerfEventMlockKb(&cur_mlock_kb) && cur_mlock_kb < mlock_kb && !SetPerfEventMlockKb(mlock_kb)) { set_prop = true; } if (GetAndroidVersion() >= kAndroidVersionQ && set_prop) { return SetPerfEventLimits(std::max(max_sample_freq_, cur_max_freq), cpu_time_max_percent_, std::max(mlock_kb, cur_mlock_kb)); } return true; } bool MonitorCommand::SetEventSelectionFlags() { event_selection_set_.SampleIdAll(); event_selection_set_.WakeupPerSample(); 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; } } return true; } bool MonitorCommand::ProcessRecord(Record* record) { UpdateRecord(record); last_record_timestamp_ = std::max(last_record_timestamp_, record->Timestamp()); // In system wide recording, maps are dumped when they are needed by records. if (system_wide_collection_ && !DumpMapsForRecord(record)) { return false; } if (record->type() == PERF_RECORD_SAMPLE) { auto& r = *static_cast(record); // Record filter check should go after DumpMapsForRecord(). Otherwise, process/thread name // filters don't work in system wide collection. if (!record_filter_.Check(r)) { return true; } // AdjustCallChainGeneratedByKernel() should go before UnwindRecord(). // Because we don't want to adjust callchains generated by dwarf unwinder. if (fp_callchain_sampling_ || dwarf_callchain_sampling_) { r.AdjustCallChainGeneratedByKernel(); if (!UnwindRecord(r)) { return false; } } DumpSampleRecord(r); if (fp_callchain_sampling_ || dwarf_callchain_sampling_) { DumpSampleCallchain(r); } sample_record_count_++; } else { // Other types of record are forwarded to the thread tree to build the // representation of each processes (mmap, comm, etc). thread_tree_.Update(*record); } return true; } void MonitorCommand::DumpSampleRecord(const SampleRecord& sr) { std::string output("sample"); StringAppendF(&output, " name=%s", event_names_[sr.id_data.id].c_str()); StringAppendF(&output, " ip=%p", reinterpret_cast(sr.ip_data.ip)); SymbolInfo s = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, sr.ip_data.ip, sr.InKernel()); StringAppendF(&output, " symbol=%s (%s[+%" PRIx64 "])", s.symbol->DemangledName(), s.dso->Path().c_str(), s.vaddr_in_file); StringAppendF(&output, " pid=%u tid=%u", sr.tid_data.pid, sr.tid_data.tid); StringAppendF(&output, " cpu=%u", sr.cpu_data.cpu); printf("%s\n", output.c_str()); fflush(stdout); } void MonitorCommand::DumpSampleCallchain(const SampleRecord& sr) { bool in_kernel = sr.InKernel(); if (sr.sample_type & PERF_SAMPLE_CALLCHAIN) { for (size_t i = 0; i < sr.callchain_data.ip_nr; ++i) { if (sr.callchain_data.ips[i] >= PERF_CONTEXT_MAX) { if (sr.callchain_data.ips[i] == PERF_CONTEXT_USER) { in_kernel = false; } continue; } SymbolInfo s = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, sr.callchain_data.ips[i], in_kernel); std::string output("sample callchain"); StringAppendF(&output, " %s (%s[+%" PRIx64 "])", s.symbol->DemangledName(), s.dso->Path().c_str(), s.vaddr_in_file); printf("%s\n", output.c_str()); } fflush(stdout); } } SymbolInfo MonitorCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, bool in_kernel) { ThreadEntry* thread = thread_tree_.FindThreadOrNew(pid, tid); const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel); SymbolInfo info; info.symbol = thread_tree_.FindSymbol(map, ip, &info.vaddr_in_file, &info.dso); return info; } bool MonitorCommand::DumpMapsForRecord(Record* record) { if (record->type() == PERF_RECORD_SAMPLE) { pid_t pid = static_cast(record)->tid_data.pid; if (dumped_processes_.find(pid) == dumped_processes_.end()) { // Dump map info and all thread names for that process. if (!map_record_reader_->ReadProcessMaps(pid, last_record_timestamp_)) { return false; } dumped_processes_.insert(pid); } } return true; } void MonitorCommand::UpdateRecord(Record* record) { if (record->type() == PERF_RECORD_COMM) { auto r = static_cast(record); if (r->data->pid == r->data->tid) { std::string s = GetCompleteProcessName(r->data->pid); if (!s.empty()) { r->SetCommandName(s); } } } } bool MonitorCommand::UnwindRecord(SampleRecord& r) { 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.GetValidStackSize() > 0)) { ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); RegSet regs(r.regs_user_data.abi, r.regs_user_data.reg_mask, r.regs_user_data.regs); std::vector ips; std::vector sps; if (!offline_unwinder_->UnwindCallChain(*thread, regs, r.stack_user_data.data, r.GetValidStackSize(), &ips, &sps)) { return false; } r.ReplaceRegAndStackWithCallChain(ips); } return true; } } // namespace void RegisterMonitorCommand() { RegisterCommand("monitor", [] { return std::unique_ptr(new MonitorCommand()); }); } } // namespace simpleperf