Reading posix.perf_event_open always returns 0

I’ve been trying to reimplement (steal) some code from Andrew’s poop library. But i can’t seem to get any readings from the perf monitoring events, it always returns 0. I can’t really figure out why, i tried running my code as sudo as well but no dice. Andrew’s poop library works fine so my system configuration should allow it.

for (perf_measurements, &self.perf_fds) |measurement, *perf_fd| {
    var attr: std.os.linux.perf_event_attr = .{
        .type = PERF.TYPE.HARDWARE,
        .config = @intFromEnum(measurement.config),
        .flags = .{
            .disabled = true,
            .exclude_kernel = true,
            .exclude_hv = true,
            .inherit = true,
            .enable_on_exec = true,
        },
    };

    perf_fd.* = std.posix.perf_event_open(&attr, 0, -1, self.perf_fds[0], PERF.FLAG.FD_NO_GROUP) catch |err| {
        std.debug.panic("unable to open perf event: {s}\n", .{@errorName(err)});
    };
    _ = std.os.linux.ioctl(perf_fd.*, PERF.EVENT_IOC.ENABLE, @intFromPtr(perf_fd));

}

_ = std.os.linux.ioctl(self.perf_fds[0], PERF.EVENT_IOC.DISABLE, PERF.IOC_FLAG_GROUP);
_ = std.os.linux.ioctl(self.perf_fds[0], PERF.EVENT_IOC.RESET, PERF.IOC_FLAG_GROUP);

self.benchmark.start_ns = self.benchmark.timer.read();

//
// Some testing code.
//

const end_ns = self.benchmark.timer.read() - self.benchmark.start_ns;

_ = std.os.linux.ioctl(self.perf_fds[0], PERF.EVENT_IOC.DISABLE, PERF.IOC_FLAG_GROUP);

self.benchmark.samples_buf[self.cur_sample] = .{
    .wall_time = end_ns,
    .cpu_cycles = readPerfFd(self.perf_fds[0]),
    .instructions = readPerfFd(self.perf_fds[1]),
    .cache_references = readPerfFd(self.perf_fds[2]),
    .cache_misses = readPerfFd(self.perf_fds[3]),
    .branch_misses = readPerfFd(self.perf_fds[4]),
};
for (&self.perf_fds) |*perf_fd| {
    std.posix.close(perf_fd.*);
    perf_fd.* = -1;
}

enable_on_exec means “next exec enables”. Do you exec a process in your testing code?

ah, no I’m not spawning a process. I’ve tried with FD_NO_GROUP as well. Sorry, should have made it clear that i tried all options :slight_smile:

Edit: okay i might have gotten a little closer to get it to working. I added this line (also updated original post)

_ = std.os.linux.ioctl(perf_fd.*, PERF.EVENT_IOC.ENABLE, @intFromPtr(perf_fd));

Cpu cycles still 0 though.

Okay i figured it out.

for (perf_measurements, &self.perf_fds) |measurement, *perf_fd| {
    var attr: std.os.linux.perf_event_attr = .{
        .type = PERF.TYPE.HARDWARE,
        .config = @intFromEnum(measurement.config),
        .flags = .{
            .disabled = true,
            .exclude_kernel = true,
            .exclude_hv = true,
            .inherit = true,
            .enable_on_exec = false,
        },
    };

    perf_fd.* = std.posix.perf_event_open(&attr, 0, -1, self.perf_fds[0], PERF.FLAG.FD_NO_GROUP) catch |err| {
        std.debug.panic("unable to open perf event: {s}\n", .{@errorName(err)});
    };
    _ = std.os.linux.ioctl(perf_fd.*, PERF.EVENT_IOC.ENABLE, @intFromPtr(perf_fd));
}

_ = std.os.linux.ioctl(self.perf_fds[0], PERF.EVENT_IOC.RESET, PERF.IOC_FLAG_GROUP);
_ = std.os.linux.ioctl(self.perf_fds[0], PERF.EVENT_IOC.ENABLE, PERF.IOC

2 Likes

I’ll also add that there are security settings in certain distros (like Ubuntu) that often require being lowered before you can track certain things. In particular, it prevents perf_event_open.

It’s called kernel.perf_event_paranoid: kernel - What does perf paranoia level four do? - Ask Ubuntu

When I lowered my settings, I was able to incorporate hardware counters. Please read up on it before just lowering it arbitrarily. It may also get reset when you reboot.

3 Likes