// Shared benchmark plumbing: machine attribution (PERF_PLAN M6), repetition // statistics (M3), and context-switch capture. // // The attribution is not decoration. A result taken under the powersave // governor or on battery is not comparable with one taken on AC under // performance, and a stored CSV that does not say which it was cannot be // argued about later. #pragma once #include #include #include #include #include #include #include namespace bench { inline int hw_units() { unsigned n = std::thread::hardware_concurrency(); return n ? static_cast(n) : 1; } inline std::string read_line_of(const char* path) { std::FILE* f = std::fopen(path, "r"); if (!f) return "unknown"; char buf[128] = {0}; if (!std::fgets(buf, sizeof buf, f)) { std::fclose(f); return "unknown"; } std::fclose(f); std::string s(buf); while (!s.empty() && (s.back() == '\n' || s.back() == ' ')) s.pop_back(); return s.empty() ? "unknown" : s; } inline std::string ac_state() { for (const char* p : {"/sys/class/power_supply/AC/online", "/sys/class/power_supply/AC0/online", "/sys/class/power_supply/ACAD/online", "/sys/class/power_supply/ADP1/online"}) { std::string v = read_line_of(p); if (v != "unknown") return v == "1" ? "ac" : "battery"; } return "unknown"; } // M6 — emitted to both streams: the CSV so a stored result can be attributed, // the terminal so a run under the wrong governor is noticed while it happens. inline void print_environment(const std::string& config_line) { const std::string gov = read_line_of( "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); const std::string ac = ac_state(); for (std::FILE* out : {stdout, stderr}) { std::fprintf(out, "# nproc=%d governor=%s power=%s\n", hw_units(), gov.c_str(), ac.c_str()); if (!config_line.empty()) std::fprintf(out, "# %s\n", config_line.c_str()); #if defined(__GNUC__) && !defined(__clang__) std::fprintf(out, "# compiler=gcc-%d.%d.%d\n", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); #elif defined(__clang__) std::fprintf(out, "# compiler=clang-%d.%d.%d\n", __clang_major__, __clang_minor__, __clang_patchlevel__); #endif } if (gov != "performance" || ac == "battery") std::fprintf(stderr, "# WARNING: governor=%s power=%s — results are not comparable with\n" "# a run on AC power under the performance governor.\n", gov.c_str(), ac.c_str()); } inline double percentile(std::vector v, double p) { if (v.empty()) return 0; std::sort(v.begin(), v.end()); double idx = p * (v.size() - 1); auto lo = static_cast(std::floor(idx)); auto hi = static_cast(std::ceil(idx)); return v[lo] + (v[hi] - v[lo]) * (idx - lo); } // Process-wide context-switch counters, sampled around a timed region. // // ru_nvcsw (voluntary) is the cheap answer to PERF_PLAN B2: a thread that // blocks on a condition variable books a voluntary switch, so voluntary // switches per dispatch is, near enough, sleeps per dispatch. ru_nivcsw // (involuntary) is preemption, which is what oversubscription looks like (A3). struct RusageDelta { long ivcsw0 = 0, vcsw0 = 0; void start() { rusage ru{}; getrusage(RUSAGE_SELF, &ru); ivcsw0 = ru.ru_nivcsw; vcsw0 = ru.ru_nvcsw; } void finish(long& nivcsw, long& nvcsw) const { rusage ru{}; getrusage(RUSAGE_SELF, &ru); nivcsw = ru.ru_nivcsw - ivcsw0; nvcsw = ru.ru_nvcsw - vcsw0; } }; } // namespace bench