1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
/*
* Performance measurements
*
* Copyright (C) 2026 Julian Andres Klode <jak@debian.org>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef APT_PERF_H
#define APT_PERF_H
#if defined(APT_COMPILING_APT) && defined(__linux__)
#include <apt-pkg/macros.h>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <fcntl.h>
#include <linux/perf_event.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>
namespace APT
{
/**
* \brief A scoped object that will log the performance counters.
*
* Set the "APT_PERFORMANCE_LOG" environment variable to produce a
* JSONL file with records for various contexts, such as the solver.
*/
class PerformanceContext
{
struct measurement
{
uint32_t type;
uint64_t config;
const char *name;
};
static constexpr std::array<measurement, 10> measurements{
measurement{PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS, "instructions"},
measurement{PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, "cpu_cycles"},
measurement{PERF_TYPE_HARDWARE, PERF_COUNT_HW_REF_CPU_CYCLES, "ref_cpu_cycles"},
measurement{PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES, "cache_references"},
measurement{PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES, "cache_misses"},
measurement{PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS, "branch_instructions"},
measurement{PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES, "branch_misses"},
measurement{PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK, "cpu_clock"},
measurement{PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS, "page_faults"},
measurement{PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS, "cpu_migrations"},
};
/// Output filename
std::string out;
/// Name of the context
std::string name;
/// FDs to communicate with the kernel
std::array<int, measurements.size()> fds;
// Wrapper for the system call
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
int cpu, int group_fd, unsigned long flags)
{
return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
}
// Wrapper for the system call
static int open_perf_counter(uint32_t type, uint64_t config)
{
struct perf_event_attr pe;
memset(&pe, 0, sizeof(struct perf_event_attr));
pe.type = type;
pe.size = sizeof(struct perf_event_attr);
pe.config = config;
pe.disabled = 1;
pe.exclude_kernel = 1;
pe.exclude_hv = 1;
int fd = perf_event_open(&pe, 0, -1, -1, 0);
return fd;
}
public:
/// Construct a new scoped performance context
PerformanceContext(std::string name) : name(name)
{
if (auto out = getenv("APT_PERFORMANCE_LOG"))
this->out = out;
if (likely(out.empty()))
return;
for (size_t i = 0; i < measurements.size(); ++i)
fds[i] = open_perf_counter(measurements[i].type, measurements[i].config);
for (auto fd : fds)
must_succeed(fd == -1 || ioctl(fd, PERF_EVENT_IOC_RESET, 0) != -1);
for (auto fd : fds)
must_succeed(fd == -1 || ioctl(fd, PERF_EVENT_IOC_ENABLE, 0) != -1);
}
/// Collect the results and store them in the specified performance file
~PerformanceContext()
{
if (likely(out.empty()))
return;
for (auto fd : fds)
must_succeed(fd == -1 || ioctl(fd, PERF_EVENT_IOC_DISABLE, 0) != -1);
std::array<long long, measurements.size()> values;
for (size_t i = 0; i < measurements.size(); ++i)
must_succeed(fds[i] == -1 || read(fds[i], &values[i], sizeof(values[i])) == sizeof(values[i]));
for (auto fd : fds)
must_succeed(fd == -1 || close(fd) == 0);
std::stringstream ss;
ss.imbue(std::locale::classic());
ss << "{\"context\": " << '"' << name << '"';
for (size_t i = 0; i < measurements.size(); ++i)
{
ss << ", ";
ss << '"' << measurements[i].name << '"' << ": " << values[i];
}
ss << "}\n";
std::string entry = ss.str();
// Atomically append a line to the JSONL file, allowing all users to read it
int fd = open(out.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
must_succeed(fd != -1);
must_succeed(flock(fd, LOCK_EX) == 0);
must_succeed(write(fd, entry.c_str(), entry.size()) == static_cast<ssize_t>(entry.size()));
must_succeed(flock(fd, LOCK_UN) == 0);
must_succeed(close(fd) == 0);
}
};
} // namespace APT
#else
namespace APT
{
struct PerformanceContext
{
PerformanceContext(const char *) {};
~PerformanceContext() {};
};
} // namespace APT
#endif
#endif
|