Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 77 additions & 22 deletions src/PerfCounters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <err.h>
#include <fcntl.h>
#include <linux/perf_event.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand All @@ -27,6 +28,7 @@ namespace rr {

static bool attributes_initialized;
static struct perf_event_attr ticks_attr;
static struct perf_event_attr cycles_attr;
static struct perf_event_attr page_faults_attr;
static struct perf_event_attr hw_interrupts_attr;
static struct perf_event_attr instructions_retired_attr;
Expand Down Expand Up @@ -171,6 +173,8 @@ static void init_attributes() {
}

init_perf_event_attr(&ticks_attr, PERF_TYPE_RAW, pmu->rcb_cntr_event);
init_perf_event_attr(&cycles_attr, PERF_TYPE_HARDWARE,
PERF_COUNT_HW_CPU_CYCLES);
init_perf_event_attr(&instructions_retired_attr, PERF_TYPE_RAW,
pmu->rinsn_cntr_event);
init_perf_event_attr(&hw_interrupts_attr, PERF_TYPE_RAW,
Expand Down Expand Up @@ -211,36 +215,77 @@ static ScopedFd start_counter(pid_t tid, int group_fd,
return fd;
}

void PerfCounters::reset(Ticks ticks_period) {
assert(ticks_period >= 0);
static bool has_ioc_period_bug() {
static bool did_test = false;
static bool bug_detected = true;
if (did_test)
return bug_detected;

// Start a cycles counter
struct perf_event_attr attr = rr::cycles_attr;
attr.sample_period = 0xfffffff;
attr.exclude_kernel = 0;
attr.disabled = 0;
ScopedFd bug_fd = start_counter(0, -1, &attr);

uint64_t new_period = 1;
if (ioctl(bug_fd, PERF_EVENT_IOC_PERIOD, &new_period)) {
FATAL() << "ioctl(PERF_EVENT_IOC_PERIOD) failed";
}

stop();
struct pollfd poll_bug_fd = {.fd = bug_fd, .events = POLL_IN, .revents = 0 };
poll(&poll_bug_fd, 1, 0);

struct perf_event_attr attr = rr::ticks_attr;
attr.sample_period = ticks_period;
fd_ticks = start_counter(tid, -1, &attr);
bug_detected = poll_bug_fd.revents == 0;

struct f_owner_ex own;
own.type = F_OWNER_TID;
own.pid = tid;
if (fcntl(fd_ticks, F_SETOWN_EX, &own)) {
FATAL() << "Failed to SETOWN_EX ticks event fd";
}
if (fcntl(fd_ticks, F_SETFL, O_ASYNC) ||
fcntl(fd_ticks, F_SETSIG, PerfCounters::TIME_SLICE_SIGNAL)) {
did_test = true;
return bug_detected;
}

static void make_counter_async(ScopedFd& fd, int signal) {
if (fcntl(fd, F_SETFL, O_ASYNC) || fcntl(fd, F_SETSIG, signal)) {
FATAL() << "Failed to make ticks counter ASYNC with sig"
<< signal_name(PerfCounters::TIME_SLICE_SIGNAL);
<< signal_name(signal);
}
}

void PerfCounters::reset(Ticks ticks_period) {
assert(ticks_period >= 0);

if (extra_perf_counters_enabled()) {
int group_leader = fd_ticks;
fd_hw_interrupts = start_counter(tid, group_leader, &hw_interrupts_attr);
fd_instructions_retired =
start_counter(tid, group_leader, &instructions_retired_attr);
fd_page_faults = start_counter(tid, group_leader, &page_faults_attr);
if (!started) {
struct perf_event_attr attr = rr::ticks_attr;
attr.sample_period = ticks_period;
fd_ticks = start_counter(tid, -1, &attr);

struct f_owner_ex own;
own.type = F_OWNER_TID;
own.pid = tid;
if (fcntl(fd_ticks, F_SETOWN_EX, &own)) {
FATAL() << "Failed to SETOWN_EX ticks event fd";
}
make_counter_async(fd_ticks, PerfCounters::TIME_SLICE_SIGNAL);

if (extra_perf_counters_enabled()) {
int group_leader = fd_ticks;
fd_hw_interrupts = start_counter(tid, group_leader, &hw_interrupts_attr);
fd_instructions_retired =
start_counter(tid, group_leader, &instructions_retired_attr);
fd_page_faults = start_counter(tid, group_leader, &page_faults_attr);
}
} else {
if (ioctl(fd_ticks, PERF_EVENT_IOC_RESET, 0)) {
FATAL() << "ioctl(PERF_EVENT_IOC_RESET) failed";
}
if (ioctl(fd_ticks, PERF_EVENT_IOC_PERIOD, &ticks_period)) {
FATAL() << "ioctl(PERF_EVENT_IOC_PERIOD) failed";
}
if (ioctl(fd_ticks, PERF_EVENT_IOC_ENABLE, 0)) {
FATAL() << "ioctl(PERF_EVENT_IOC_ENABLE) failed";
}
}

started = true;
counting = true;
}

void PerfCounters::set_tid(pid_t tid) {
Expand All @@ -260,6 +305,15 @@ void PerfCounters::stop() {
fd_instructions_retired.close();
}

void PerfCounters::stop_counting() {
counting = false;
if (has_ioc_period_bug()) {
stop();
} else {
ioctl(fd_ticks, PERF_EVENT_IOC_DISABLE, 0);
}
}

static int64_t read_counter(ScopedFd& fd) {
int64_t val;
ssize_t nread = read(fd, &val, sizeof(val));
Expand All @@ -268,7 +322,8 @@ static int64_t read_counter(ScopedFd& fd) {
}

Ticks PerfCounters::read_ticks() {
return started ? read_counter(fd_ticks) : 0;
uint64_t val = started && counting ? read_counter(fd_ticks) : 0;
return val;
}

PerfCounters::Extra PerfCounters::read_extra() {
Expand Down
8 changes: 8 additions & 0 deletions src/PerfCounters.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ class PerfCounters {
*/
void stop();

/**
* Suspend counting until the next reset. This may or may not actually stop
* the performance counters, depending on whether or not this is required
* for correctness on this kernel version.
*/
void stop_counting();

/**
* Read the current value of the ticks counter.
*/
Expand All @@ -83,6 +90,7 @@ class PerfCounters {
Extra read_extra();

static const struct perf_event_attr& ticks_attr();
bool counting;

private:
pid_t tid;
Expand Down
10 changes: 5 additions & 5 deletions src/Task.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1276,12 +1276,12 @@ void Task::emulate_syscall_entry(const Registers& regs) {
}

void Task::did_waitpid(WaitStatus status) {
Ticks more_ticks = hpc.read_ticks();
// Stop PerfCounters ASAP to reduce the possibility that due to bugs or
// whatever they pick up something spurious later.
hpc.stop();
ticks += more_ticks;
Ticks more_ticks = hpc.counting ? hpc.read_ticks() : 0;
// We stop counting here because there may be things we want to do to the
// tracee that would otherwise generate ticks.
hpc.stop_counting();
session().accumulate_ticks_processed(more_ticks);
ticks += more_ticks;

// After PTRACE_INTERRUPT, any next two stops may be a group stop caused by
// that PTRACE_INTERRUPT (or neither may be). This is because PTRACE_INTERRUPT
Expand Down
14 changes: 14 additions & 0 deletions src/VirtualPerfCounterMonitor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ bool VirtualPerfCounterMonitor::emulate_ioctl(RecordTask* t, uint64_t* result) {
*result = 0;
enabled = true;
break;
case PERF_EVENT_IOC_DISABLE:
*result = 0;
enabled = false;
break;
case PERF_EVENT_IOC_RESET: {
*result = 0;
RecordTask* target = t->session().find_task(target_tuid);
initial_ticks = target->tick_count();
} break;
case PERF_EVENT_IOC_PERIOD: {
*result = 0;
// Nominally we'd reset the interrupt here, but since we don't support
// that yet, just ignore it.
} break;
default:
ASSERT(t, false) << "Unsupported perf event ioctl "
<< HEX((int)t->regs().arg2());
Expand Down