You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
856 lines
27 KiB
856 lines
27 KiB
/*
|
|
* 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 "event_selection_set.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
|
|
#include "ETMRecorder.h"
|
|
#include "IOEventLoop.h"
|
|
#include "RecordReadThread.h"
|
|
#include "environment.h"
|
|
#include "event_attr.h"
|
|
#include "event_type.h"
|
|
#include "perf_regs.h"
|
|
#include "tracing.h"
|
|
#include "utils.h"
|
|
|
|
namespace simpleperf {
|
|
|
|
using android::base::StringPrintf;
|
|
|
|
bool IsBranchSamplingSupported() {
|
|
const EventType* type = FindEventTypeByName("cpu-cycles");
|
|
if (type == nullptr) {
|
|
return false;
|
|
}
|
|
perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
|
|
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
|
|
attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY;
|
|
return IsEventAttrSupported(attr, type->name);
|
|
}
|
|
|
|
bool IsDwarfCallChainSamplingSupported() {
|
|
const EventType* type = FindEventTypeByName("cpu-clock");
|
|
if (type == nullptr) {
|
|
return false;
|
|
}
|
|
perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
|
|
attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
|
|
attr.exclude_callchain_user = 1;
|
|
attr.sample_regs_user = GetSupportedRegMask(GetBuildArch());
|
|
attr.sample_stack_user = 8192;
|
|
return IsEventAttrSupported(attr, type->name);
|
|
}
|
|
|
|
bool IsDumpingRegsForTracepointEventsSupported() {
|
|
if (auto version = GetKernelVersion(); version && version.value() >= std::make_pair(4, 2)) {
|
|
// Kernel >= 4.2 has patch "5b09a094f2 arm64: perf: Fix callchain parse error with kernel
|
|
// tracepoint events". So no need to test.
|
|
return true;
|
|
}
|
|
const EventType* event_type = FindEventTypeByName("sched:sched_switch", false);
|
|
if (event_type == nullptr) {
|
|
return false;
|
|
}
|
|
std::atomic<bool> done(false);
|
|
std::atomic<pid_t> thread_id(0);
|
|
std::thread thread([&]() {
|
|
thread_id = gettid();
|
|
while (!done) {
|
|
usleep(1);
|
|
}
|
|
usleep(1); // Make a sched out to generate one sample.
|
|
});
|
|
while (thread_id == 0) {
|
|
usleep(1);
|
|
}
|
|
perf_event_attr attr = CreateDefaultPerfEventAttr(*event_type);
|
|
attr.freq = 0;
|
|
attr.sample_period = 1;
|
|
std::unique_ptr<EventFd> event_fd =
|
|
EventFd::OpenEventFile(attr, thread_id, -1, nullptr, event_type->name);
|
|
if (event_fd == nullptr || !event_fd->CreateMappedBuffer(4, true)) {
|
|
done = true;
|
|
thread.join();
|
|
return false;
|
|
}
|
|
done = true;
|
|
thread.join();
|
|
|
|
// There are small chances that we don't see samples immediately after joining the thread on
|
|
// cuttlefish, probably due to data synchronization between cpus. To avoid flaky tests, use a
|
|
// loop to wait for samples.
|
|
for (int timeout = 0; timeout < 1000; timeout++) {
|
|
std::vector<char> buffer = event_fd->GetAvailableMmapData();
|
|
std::vector<std::unique_ptr<Record>> records =
|
|
ReadRecordsFromBuffer(attr, buffer.data(), buffer.size());
|
|
for (auto& r : records) {
|
|
if (r->type() == PERF_RECORD_SAMPLE) {
|
|
auto& record = *static_cast<SampleRecord*>(r.get());
|
|
return record.ip_data.ip != 0;
|
|
}
|
|
}
|
|
usleep(1);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsSettingClockIdSupported() {
|
|
// Do the real check only once and keep the result in a static variable.
|
|
static int is_supported = -1;
|
|
if (is_supported == -1) {
|
|
const EventType* type = FindEventTypeByName("cpu-clock");
|
|
if (type == nullptr) {
|
|
is_supported = 0;
|
|
} else {
|
|
// Check if the kernel supports setting clockid, which was added in kernel 4.0. Just check
|
|
// with one clockid is enough. Because all needed clockids were supported before kernel 4.0.
|
|
perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
|
|
attr.use_clockid = 1;
|
|
attr.clockid = CLOCK_MONOTONIC;
|
|
is_supported = IsEventAttrSupported(attr, type->name) ? 1 : 0;
|
|
}
|
|
}
|
|
return is_supported;
|
|
}
|
|
|
|
bool IsMmap2Supported() {
|
|
const EventType* type = FindEventTypeByName("cpu-clock");
|
|
if (type == nullptr) {
|
|
return false;
|
|
}
|
|
perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
|
|
attr.mmap2 = 1;
|
|
return IsEventAttrSupported(attr, type->name);
|
|
}
|
|
|
|
std::string AddrFilter::ToString() const {
|
|
switch (type) {
|
|
case FILE_RANGE:
|
|
return StringPrintf("filter 0x%" PRIx64 "/0x%" PRIx64 "@%s", addr, size, file_path.c_str());
|
|
case AddrFilter::FILE_START:
|
|
return StringPrintf("start 0x%" PRIx64 "@%s", addr, file_path.c_str());
|
|
case AddrFilter::FILE_STOP:
|
|
return StringPrintf("stop 0x%" PRIx64 "@%s", addr, file_path.c_str());
|
|
case AddrFilter::KERNEL_RANGE:
|
|
return StringPrintf("filter 0x%" PRIx64 "/0x%" PRIx64, addr, size);
|
|
case AddrFilter::KERNEL_START:
|
|
return StringPrintf("start 0x%" PRIx64, addr);
|
|
case AddrFilter::KERNEL_STOP:
|
|
return StringPrintf("stop 0x%" PRIx64, addr);
|
|
}
|
|
}
|
|
|
|
EventSelectionSet::EventSelectionSet(bool for_stat_cmd)
|
|
: for_stat_cmd_(for_stat_cmd), loop_(new IOEventLoop) {}
|
|
|
|
EventSelectionSet::~EventSelectionSet() {}
|
|
|
|
bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_name, bool first_event,
|
|
EventSelection* selection) {
|
|
std::unique_ptr<EventTypeAndModifier> event_type = ParseEventType(event_name);
|
|
if (event_type == nullptr) {
|
|
return false;
|
|
}
|
|
if (for_stat_cmd_) {
|
|
if (event_type->event_type.name == "cpu-clock" || event_type->event_type.name == "task-clock") {
|
|
if (event_type->exclude_user || event_type->exclude_kernel) {
|
|
LOG(ERROR) << "Modifier u and modifier k used in event type " << event_type->event_type.name
|
|
<< " are not supported by the kernel.";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
selection->event_type_modifier = *event_type;
|
|
selection->event_attr = CreateDefaultPerfEventAttr(event_type->event_type);
|
|
selection->event_attr.exclude_user = event_type->exclude_user;
|
|
selection->event_attr.exclude_kernel = event_type->exclude_kernel;
|
|
selection->event_attr.exclude_hv = event_type->exclude_hv;
|
|
selection->event_attr.exclude_host = event_type->exclude_host;
|
|
selection->event_attr.exclude_guest = event_type->exclude_guest;
|
|
selection->event_attr.precise_ip = event_type->precise_ip;
|
|
if (IsEtmEventType(event_type->event_type.type)) {
|
|
auto& etm_recorder = ETMRecorder::GetInstance();
|
|
if (!etm_recorder.CheckEtmSupport()) {
|
|
return false;
|
|
}
|
|
ETMRecorder::GetInstance().SetEtmPerfEventAttr(&selection->event_attr);
|
|
}
|
|
bool set_default_sample_freq = false;
|
|
if (!for_stat_cmd_) {
|
|
if (event_type->event_type.type == PERF_TYPE_TRACEPOINT) {
|
|
selection->event_attr.freq = 0;
|
|
selection->event_attr.sample_period = DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT;
|
|
} else if (IsEtmEventType(event_type->event_type.type)) {
|
|
// ETM recording has no sample frequency to adjust. Using sample frequency only wastes time
|
|
// enabling/disabling etm devices. So don't adjust frequency by default.
|
|
selection->event_attr.freq = 0;
|
|
selection->event_attr.sample_period = 1;
|
|
} else {
|
|
selection->event_attr.freq = 1;
|
|
// Set default sample freq here may print msg "Adjust sample freq to max allowed sample
|
|
// freq". But this is misleading. Because default sample freq may not be the final sample
|
|
// freq we use. So use minimum sample freq (1) here.
|
|
selection->event_attr.sample_freq = 1;
|
|
set_default_sample_freq = true;
|
|
}
|
|
// We only need to dump mmap and comm records for the first event type. Because all event types
|
|
// are monitoring the same processes.
|
|
if (first_event) {
|
|
selection->event_attr.mmap = 1;
|
|
selection->event_attr.comm = 1;
|
|
if (IsMmap2Supported()) {
|
|
selection->event_attr.mmap2 = 1;
|
|
}
|
|
}
|
|
}
|
|
// PMU events are provided by kernel, so they should be supported
|
|
if (!event_type->event_type.IsPmuEvent() &&
|
|
!IsEventAttrSupported(selection->event_attr, selection->event_type_modifier.name)) {
|
|
LOG(ERROR) << "Event type '" << event_type->name << "' is not supported on the device";
|
|
return false;
|
|
}
|
|
if (set_default_sample_freq) {
|
|
selection->event_attr.sample_freq = DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT;
|
|
}
|
|
|
|
selection->event_fds.clear();
|
|
|
|
for (const auto& group : groups_) {
|
|
for (const auto& sel : group) {
|
|
if (sel.event_type_modifier.name == selection->event_type_modifier.name) {
|
|
LOG(ERROR) << "Event type '" << sel.event_type_modifier.name << "' appears more than once";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EventSelectionSet::AddEventType(const std::string& event_name, size_t* group_id) {
|
|
return AddEventGroup(std::vector<std::string>(1, event_name), group_id);
|
|
}
|
|
|
|
bool EventSelectionSet::AddEventGroup(const std::vector<std::string>& event_names,
|
|
size_t* group_id) {
|
|
EventSelectionGroup group;
|
|
bool first_event = groups_.empty();
|
|
bool first_in_group = true;
|
|
for (const auto& event_name : event_names) {
|
|
EventSelection selection;
|
|
if (!BuildAndCheckEventSelection(event_name, first_event, &selection)) {
|
|
return false;
|
|
}
|
|
if (IsEtmEventType(selection.event_attr.type)) {
|
|
has_aux_trace_ = true;
|
|
}
|
|
if (first_in_group) {
|
|
auto& event_type = selection.event_type_modifier.event_type;
|
|
if (event_type.IsPmuEvent()) {
|
|
selection.allowed_cpus = event_type.GetPmuCpumask();
|
|
}
|
|
}
|
|
first_event = false;
|
|
first_in_group = false;
|
|
group.push_back(std::move(selection));
|
|
}
|
|
groups_.push_back(std::move(group));
|
|
UnionSampleType();
|
|
if (group_id != nullptr) {
|
|
*group_id = groups_.size() - 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<const EventType*> EventSelectionSet::GetEvents() const {
|
|
std::vector<const EventType*> result;
|
|
for (const auto& group : groups_) {
|
|
for (const auto& selection : group) {
|
|
result.push_back(&selection.event_type_modifier.event_type);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<const EventType*> EventSelectionSet::GetTracepointEvents() const {
|
|
std::vector<const EventType*> result;
|
|
for (const auto& group : groups_) {
|
|
for (const auto& selection : group) {
|
|
if (selection.event_type_modifier.event_type.type == PERF_TYPE_TRACEPOINT) {
|
|
result.push_back(&selection.event_type_modifier.event_type);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool EventSelectionSet::ExcludeKernel() const {
|
|
for (const auto& group : groups_) {
|
|
for (const auto& selection : group) {
|
|
if (!selection.event_type_modifier.exclude_kernel) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<EventAttrWithId> EventSelectionSet::GetEventAttrWithId() const {
|
|
std::vector<EventAttrWithId> result;
|
|
for (const auto& group : groups_) {
|
|
for (const auto& selection : group) {
|
|
EventAttrWithId attr_id;
|
|
attr_id.attr = &selection.event_attr;
|
|
for (const auto& fd : selection.event_fds) {
|
|
attr_id.ids.push_back(fd->Id());
|
|
}
|
|
result.push_back(attr_id);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::unordered_map<uint64_t, std::string> EventSelectionSet::GetEventNamesById() const {
|
|
std::unordered_map<uint64_t, std::string> result;
|
|
for (const auto& group : groups_) {
|
|
for (const auto& selection : group) {
|
|
for (const auto& fd : selection.event_fds) {
|
|
result[fd->Id()] = selection.event_type_modifier.name;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Union the sample type of different event attrs can make reading sample
|
|
// records in perf.data easier.
|
|
void EventSelectionSet::UnionSampleType() {
|
|
uint64_t sample_type = 0;
|
|
for (const auto& group : groups_) {
|
|
for (const auto& selection : group) {
|
|
sample_type |= selection.event_attr.sample_type;
|
|
}
|
|
}
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
selection.event_attr.sample_type = sample_type;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EventSelectionSet::SetEnableOnExec(bool enable) {
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
// If sampling is enabled on exec, then it is disabled at startup,
|
|
// otherwise it should be enabled at startup. Don't use
|
|
// ioctl(PERF_EVENT_IOC_ENABLE) to enable it after perf_event_open().
|
|
// Because some android kernels can't handle ioctl() well when cpu-hotplug
|
|
// happens. See http://b/25193162.
|
|
if (enable) {
|
|
selection.event_attr.enable_on_exec = 1;
|
|
selection.event_attr.disabled = 1;
|
|
} else {
|
|
selection.event_attr.enable_on_exec = 0;
|
|
selection.event_attr.disabled = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EventSelectionSet::GetEnableOnExec() {
|
|
for (const auto& group : groups_) {
|
|
for (const auto& selection : group) {
|
|
if (selection.event_attr.enable_on_exec == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void EventSelectionSet::SampleIdAll() {
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
selection.event_attr.sample_id_all = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EventSelectionSet::SetSampleSpeed(size_t group_id, const SampleSpeed& speed) {
|
|
CHECK_LT(group_id, groups_.size());
|
|
for (auto& selection : groups_[group_id]) {
|
|
if (speed.UseFreq()) {
|
|
selection.event_attr.freq = 1;
|
|
selection.event_attr.sample_freq = speed.sample_freq;
|
|
} else {
|
|
selection.event_attr.freq = 0;
|
|
selection.event_attr.sample_period = speed.sample_period;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EventSelectionSet::SetBranchSampling(uint64_t branch_sample_type) {
|
|
if (branch_sample_type != 0 &&
|
|
(branch_sample_type & (PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
|
|
PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_CALL)) == 0) {
|
|
LOG(ERROR) << "Invalid branch_sample_type: 0x" << std::hex << branch_sample_type;
|
|
return false;
|
|
}
|
|
if (branch_sample_type != 0 && !IsBranchSamplingSupported()) {
|
|
LOG(ERROR) << "branch stack sampling is not supported on this device.";
|
|
return false;
|
|
}
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
perf_event_attr& attr = selection.event_attr;
|
|
if (branch_sample_type != 0) {
|
|
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
|
|
} else {
|
|
attr.sample_type &= ~PERF_SAMPLE_BRANCH_STACK;
|
|
}
|
|
attr.branch_sample_type = branch_sample_type;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void EventSelectionSet::EnableFpCallChainSampling() {
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EventSelectionSet::EnableDwarfCallChainSampling(uint32_t dump_stack_size) {
|
|
if (!IsDwarfCallChainSamplingSupported()) {
|
|
LOG(ERROR) << "dwarf callchain sampling is not supported on this device.";
|
|
return false;
|
|
}
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
selection.event_attr.sample_type |=
|
|
PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
|
|
selection.event_attr.exclude_callchain_user = 1;
|
|
selection.event_attr.sample_regs_user = GetSupportedRegMask(GetMachineArch());
|
|
selection.event_attr.sample_stack_user = dump_stack_size;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void EventSelectionSet::SetInherit(bool enable) {
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
selection.event_attr.inherit = (enable ? 1 : 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EventSelectionSet::SetClockId(int clock_id) {
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
selection.event_attr.use_clockid = 1;
|
|
selection.event_attr.clockid = clock_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EventSelectionSet::NeedKernelSymbol() const {
|
|
return !ExcludeKernel();
|
|
}
|
|
|
|
void EventSelectionSet::SetRecordNotExecutableMaps(bool record) {
|
|
// We only need to dump non-executable mmap records for the first event type.
|
|
groups_[0][0].event_attr.mmap_data = record ? 1 : 0;
|
|
}
|
|
|
|
bool EventSelectionSet::RecordNotExecutableMaps() const {
|
|
return groups_[0][0].event_attr.mmap_data == 1;
|
|
}
|
|
|
|
void EventSelectionSet::WakeupPerSample() {
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
selection.event_attr.watermark = 0;
|
|
selection.event_attr.wakeup_events = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EventSelectionSet::SetTracepointFilter(const std::string& filter) {
|
|
// 1. Find the tracepoint event to set filter.
|
|
EventSelection* selection = nullptr;
|
|
if (!groups_.empty()) {
|
|
auto& group = groups_.back();
|
|
if (group.size() == 1) {
|
|
if (group[0].event_attr.type == PERF_TYPE_TRACEPOINT) {
|
|
selection = &group[0];
|
|
}
|
|
}
|
|
}
|
|
if (selection == nullptr) {
|
|
LOG(ERROR) << "No tracepoint event before filter: " << filter;
|
|
return false;
|
|
}
|
|
|
|
// 2. Check the format of the filter.
|
|
bool use_quote = false;
|
|
// Quotes are needed for string operands in kernel >= 4.19, probably after patch "tracing: Rewrite
|
|
// filter logic to be simpler and faster".
|
|
if (auto version = GetKernelVersion(); version && version.value() >= std::make_pair(4, 19)) {
|
|
use_quote = true;
|
|
}
|
|
|
|
FieldNameSet used_fields;
|
|
auto adjusted_filter = AdjustTracepointFilter(filter, use_quote, &used_fields);
|
|
if (!adjusted_filter) {
|
|
return false;
|
|
}
|
|
|
|
// 3. Check if used fields are available in the tracepoint event.
|
|
auto& event_type = selection->event_type_modifier.event_type;
|
|
if (auto opt_fields = GetFieldNamesForTracepointEvent(event_type); opt_fields) {
|
|
FieldNameSet& fields = opt_fields.value();
|
|
for (const auto& field : used_fields) {
|
|
if (fields.find(field) == fields.end()) {
|
|
LOG(ERROR) << "field name " << field << " used in \"" << filter << "\" doesn't exist in "
|
|
<< event_type.name << ". Available fields are "
|
|
<< android::base::Join(fields, ",");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Connect the filter to the event.
|
|
selection->tracepoint_filter = adjusted_filter.value();
|
|
return true;
|
|
}
|
|
|
|
static bool CheckIfCpusOnline(const std::vector<int>& cpus) {
|
|
std::vector<int> online_cpus = GetOnlineCpus();
|
|
for (const auto& cpu : cpus) {
|
|
if (std::find(online_cpus.begin(), online_cpus.end(), cpu) == online_cpus.end()) {
|
|
LOG(ERROR) << "cpu " << cpu << " is not online.";
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EventSelectionSet::OpenEventFilesOnGroup(EventSelectionGroup& group, pid_t tid, int cpu,
|
|
std::string* failed_event_type) {
|
|
std::vector<std::unique_ptr<EventFd>> event_fds;
|
|
// Given a tid and cpu, events on the same group should be all opened
|
|
// successfully or all failed to open.
|
|
EventFd* group_fd = nullptr;
|
|
for (auto& selection : group) {
|
|
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(
|
|
selection.event_attr, tid, cpu, group_fd, selection.event_type_modifier.name, false);
|
|
if (!event_fd) {
|
|
*failed_event_type = selection.event_type_modifier.name;
|
|
return false;
|
|
}
|
|
LOG(VERBOSE) << "OpenEventFile for " << event_fd->Name();
|
|
event_fds.push_back(std::move(event_fd));
|
|
if (group_fd == nullptr) {
|
|
group_fd = event_fds.back().get();
|
|
}
|
|
}
|
|
for (size_t i = 0; i < group.size(); ++i) {
|
|
group[i].event_fds.push_back(std::move(event_fds[i]));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static std::set<pid_t> PrepareThreads(const std::set<pid_t>& processes,
|
|
const std::set<pid_t>& threads) {
|
|
std::set<pid_t> result = threads;
|
|
for (auto& pid : processes) {
|
|
std::vector<pid_t> tids = GetThreadsInProcess(pid);
|
|
result.insert(tids.begin(), tids.end());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool EventSelectionSet::OpenEventFiles(const std::vector<int>& cpus) {
|
|
std::vector<int> monitored_cpus;
|
|
if (cpus.empty()) {
|
|
monitored_cpus = GetOnlineCpus();
|
|
} else if (cpus.size() == 1 && cpus[0] == -1) {
|
|
monitored_cpus = {-1};
|
|
} else {
|
|
if (!CheckIfCpusOnline(cpus)) {
|
|
return false;
|
|
}
|
|
monitored_cpus = cpus;
|
|
}
|
|
std::set<pid_t> threads = PrepareThreads(processes_, threads_);
|
|
for (auto& group : groups_) {
|
|
size_t success_count = 0;
|
|
std::string failed_event_type;
|
|
for (const auto tid : threads) {
|
|
const std::vector<int>* pcpus = &monitored_cpus;
|
|
if (!group[0].allowed_cpus.empty()) {
|
|
// override cpu list if event's PMU has a cpumask as those PMUs are
|
|
// agnostic to cpu and it's meaningless to specify cpus for them.
|
|
pcpus = &group[0].allowed_cpus;
|
|
}
|
|
for (const auto& cpu : *pcpus) {
|
|
if (OpenEventFilesOnGroup(group, tid, cpu, &failed_event_type)) {
|
|
success_count++;
|
|
}
|
|
}
|
|
}
|
|
// We can't guarantee to open perf event file successfully for each thread on each cpu.
|
|
// Because threads may exit between PrepareThreads() and OpenEventFilesOnGroup(), and
|
|
// cpus may be offlined between GetOnlineCpus() and OpenEventFilesOnGroup().
|
|
// So we only check that we can at least monitor one thread for each event group.
|
|
if (success_count == 0) {
|
|
int error_number = errno;
|
|
PLOG(ERROR) << "failed to open perf event file for event_type " << failed_event_type;
|
|
if (error_number == EMFILE) {
|
|
LOG(ERROR) << "Please increase hard limit of open file numbers.";
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return ApplyFilters();
|
|
}
|
|
|
|
bool EventSelectionSet::ApplyFilters() {
|
|
return ApplyAddrFilters() && ApplyTracepointFilters();
|
|
}
|
|
|
|
bool EventSelectionSet::ApplyAddrFilters() {
|
|
if (addr_filters_.empty()) {
|
|
return true;
|
|
}
|
|
if (!has_aux_trace_) {
|
|
LOG(ERROR) << "addr filters only take effect in cs-etm instruction tracing";
|
|
return false;
|
|
}
|
|
|
|
// Check filter count limit.
|
|
size_t required_etm_filter_count = 0;
|
|
for (auto& filter : addr_filters_) {
|
|
// A range filter needs two etm filters.
|
|
required_etm_filter_count +=
|
|
(filter.type == AddrFilter::FILE_RANGE || filter.type == AddrFilter::KERNEL_RANGE) ? 2 : 1;
|
|
}
|
|
size_t etm_filter_count = ETMRecorder::GetInstance().GetAddrFilterPairs() * 2;
|
|
if (etm_filter_count < required_etm_filter_count) {
|
|
LOG(ERROR) << "needed " << required_etm_filter_count << " etm filters, but only "
|
|
<< etm_filter_count << " filters are available.";
|
|
return false;
|
|
}
|
|
|
|
std::string filter_str;
|
|
for (auto& filter : addr_filters_) {
|
|
if (!filter_str.empty()) {
|
|
filter_str += ',';
|
|
}
|
|
filter_str += filter.ToString();
|
|
}
|
|
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
if (IsEtmEventType(selection.event_type_modifier.event_type.type)) {
|
|
for (auto& event_fd : selection.event_fds) {
|
|
if (!event_fd->SetFilter(filter_str)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EventSelectionSet::ApplyTracepointFilters() {
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
if (!selection.tracepoint_filter.empty()) {
|
|
for (auto& event_fd : selection.event_fds) {
|
|
if (!event_fd->SetFilter(selection.tracepoint_filter)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool ReadCounter(EventFd* event_fd, CounterInfo* counter) {
|
|
if (!event_fd->ReadCounter(&counter->counter)) {
|
|
return false;
|
|
}
|
|
counter->tid = event_fd->ThreadId();
|
|
counter->cpu = event_fd->Cpu();
|
|
return true;
|
|
}
|
|
|
|
bool EventSelectionSet::ReadCounters(std::vector<CountersInfo>* counters) {
|
|
counters->clear();
|
|
for (size_t i = 0; i < groups_.size(); ++i) {
|
|
for (auto& selection : groups_[i]) {
|
|
CountersInfo counters_info;
|
|
counters_info.group_id = i;
|
|
counters_info.event_name = selection.event_type_modifier.event_type.name;
|
|
counters_info.event_modifier = selection.event_type_modifier.modifier;
|
|
counters_info.counters = selection.hotplugged_counters;
|
|
for (auto& event_fd : selection.event_fds) {
|
|
CounterInfo counter;
|
|
if (!ReadCounter(event_fd.get(), &counter)) {
|
|
return false;
|
|
}
|
|
counters_info.counters.push_back(counter);
|
|
}
|
|
counters->push_back(counters_info);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EventSelectionSet::MmapEventFiles(size_t min_mmap_pages, size_t max_mmap_pages,
|
|
size_t aux_buffer_size, size_t record_buffer_size,
|
|
bool allow_cutting_samples, bool exclude_perf) {
|
|
record_read_thread_.reset(new simpleperf::RecordReadThread(
|
|
record_buffer_size, groups_[0][0].event_attr, min_mmap_pages, max_mmap_pages, aux_buffer_size,
|
|
allow_cutting_samples, exclude_perf));
|
|
return true;
|
|
}
|
|
|
|
bool EventSelectionSet::PrepareToReadMmapEventData(const std::function<bool(Record*)>& callback) {
|
|
// Prepare record callback function.
|
|
record_callback_ = callback;
|
|
if (!record_read_thread_->RegisterDataCallback(*loop_,
|
|
[this]() { return ReadMmapEventData(true); })) {
|
|
return false;
|
|
}
|
|
std::vector<EventFd*> event_fds;
|
|
for (auto& group : groups_) {
|
|
for (auto& selection : group) {
|
|
for (auto& event_fd : selection.event_fds) {
|
|
event_fds.push_back(event_fd.get());
|
|
}
|
|
}
|
|
}
|
|
return record_read_thread_->AddEventFds(event_fds);
|
|
}
|
|
|
|
bool EventSelectionSet::SyncKernelBuffer() {
|
|
return record_read_thread_->SyncKernelBuffer();
|
|
}
|
|
|
|
// Read records from the RecordBuffer. If with_time_limit is false, read until the RecordBuffer is
|
|
// empty, otherwise stop after 100 ms or when the record buffer is empty.
|
|
bool EventSelectionSet::ReadMmapEventData(bool with_time_limit) {
|
|
uint64_t start_time_in_ns;
|
|
if (with_time_limit) {
|
|
start_time_in_ns = GetSystemClock();
|
|
}
|
|
std::unique_ptr<Record> r;
|
|
while ((r = record_read_thread_->GetRecord()) != nullptr) {
|
|
if (!record_callback_(r.get())) {
|
|
return false;
|
|
}
|
|
if (with_time_limit && (GetSystemClock() - start_time_in_ns) >= 1e8) {
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EventSelectionSet::FinishReadMmapEventData() {
|
|
// Stop the read thread, so we don't get more records beyond current time.
|
|
if (!SyncKernelBuffer() || !record_read_thread_->StopReadThread()) {
|
|
return false;
|
|
}
|
|
if (!ReadMmapEventData(false)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void EventSelectionSet::CloseEventFiles() {
|
|
if (record_read_thread_) {
|
|
record_read_thread_->StopReadThread();
|
|
}
|
|
for (auto& group : groups_) {
|
|
for (auto& event : group) {
|
|
event.event_fds.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EventSelectionSet::StopWhenNoMoreTargets(double check_interval_in_sec) {
|
|
return loop_->AddPeriodicEvent(SecondToTimeval(check_interval_in_sec),
|
|
[&]() { return CheckMonitoredTargets(); });
|
|
}
|
|
|
|
bool EventSelectionSet::CheckMonitoredTargets() {
|
|
if (!HasSampler()) {
|
|
return loop_->ExitLoop();
|
|
}
|
|
for (const auto& tid : threads_) {
|
|
if (IsThreadAlive(tid)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (const auto& pid : processes_) {
|
|
if (IsThreadAlive(pid)) {
|
|
return true;
|
|
}
|
|
}
|
|
return loop_->ExitLoop();
|
|
}
|
|
|
|
bool EventSelectionSet::HasSampler() {
|
|
for (auto& group : groups_) {
|
|
for (auto& sel : group) {
|
|
if (!sel.event_fds.empty()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool EventSelectionSet::SetEnableEvents(bool enable) {
|
|
for (auto& group : groups_) {
|
|
for (auto& sel : group) {
|
|
for (auto& fd : sel.event_fds) {
|
|
if (!fd->SetEnableEvent(enable)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace simpleperf
|