LCOV - code coverage report
Current view: top level - benchmark - benchmark_io.cpp (source / functions) Coverage Total Hit
Test: hashbrowns Coverage Lines: 89.4 % 274 245
Test Date: 2026-04-10 19:00:18 Functions: 100.0 % 18 18
Legend: Lines: hit not hit

            Line data    Source code
       1              : // benchmark_io.cpp
       2              : // Serialization helpers (CSV/JSON write) and environment metadata capture for
       3              : // BenchmarkSuite.  Extracted from benchmark_suite.cpp to keep the run-loop
       4              : // translation unit focused on timing logic.
       5              : 
       6              : #include "benchmark_suite.h"
       7              : 
       8              : #include <algorithm>
       9              : #include <cctype>
      10              : #include <cstdio>
      11              : #include <ctime>
      12              : #include <fstream>
      13              : #include <sstream>
      14              : #include <thread>
      15              : 
      16              : #if defined(__unix__) || defined(__APPLE__)
      17              : #include <sys/utsname.h>
      18              : #endif
      19              : #ifdef __APPLE__
      20              : #include <sys/sysctl.h>
      21              : #endif
      22              : #ifdef _WIN32
      23              : #define NOMINMAX
      24              : #include <windows.h>
      25              : #endif
      26              : 
      27              : namespace hashbrowns {
      28              : 
      29              : namespace {
      30              : 
      31            8 : void write_string_array(std::ofstream& out, const std::vector<std::string>& values) {
      32            8 :     out << "[";
      33            8 :     for (std::size_t i = 0; i < values.size(); ++i) {
      34            0 :         out << "\"" << values[i] << "\"";
      35            0 :         if (i + 1 < values.size())
      36            0 :             out << ",";
      37              :     }
      38            8 :     out << "]";
      39            8 : }
      40              : 
      41            4 : void write_profile_manifest(std::ofstream& out, const BenchmarkConfig& config) {
      42            4 :     out << "    \"profile_manifest\": {\n";
      43            4 :     out << "      \"selected_profile\": \"" << config.profile_name << "\",\n";
      44            4 :     out << "      \"applied_defaults\": ";
      45            4 :     write_string_array(out, config.profile_applied_defaults);
      46            4 :     out << ",\n";
      47            4 :     out << "      \"explicit_overrides\": ";
      48            4 :     write_string_array(out, config.profile_explicit_overrides);
      49            4 :     out << "\n    }";
      50            4 : }
      51              : 
      52              : } // namespace
      53              : 
      54              : // ---------------------------------------------------------------------------
      55              : // Environment metadata helpers (file-local)
      56              : // ---------------------------------------------------------------------------
      57              : 
      58            1 : static std::string read_cpu_governor() {
      59              : #ifdef __linux__
      60            1 :     std::ifstream f("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor");
      61            1 :     std::string   g;
      62            1 :     if (f)
      63            0 :         std::getline(f, g);
      64            2 :     return g;
      65              : #else
      66              :     return "unknown";
      67              : #endif
      68            1 : }
      69              : 
      70            1 : static std::string git_commit_sha() {
      71              : #ifdef _WIN32
      72              :     return "unknown";
      73              : #else
      74            1 :     FILE* pipe = popen("git rev-parse --short HEAD 2>/dev/null", "r");
      75            1 :     if (!pipe)
      76            0 :         return "unknown";
      77            1 :     char        buf[64];
      78            1 :     std::string out;
      79            1 :     if (fgets(buf, sizeof(buf), pipe))
      80            1 :         out = buf;
      81            1 :     pclose(pipe);
      82            1 :     if (!out.empty() && out.back() == '\n')
      83            1 :         out.pop_back();
      84            1 :     return out.empty() ? "unknown" : out;
      85              : #endif
      86            1 : }
      87              : 
      88            1 : static std::string cpu_model() {
      89              : #ifdef __linux__
      90            1 :     std::ifstream in("/proc/cpuinfo");
      91            1 :     std::string   line;
      92            5 :     while (std::getline(in, line)) {
      93            5 :         auto pos = line.find(":");
      94            5 :         if (pos == std::string::npos)
      95            0 :             continue;
      96            5 :         auto key = line.substr(0, pos);
      97            5 :         if (key.find("model name") != std::string::npos) {
      98            1 :             std::string val = line.substr(pos + 1);
      99            1 :             val.erase(0, val.find_first_not_of(" \t"));
     100            1 :             return val;
     101            1 :         }
     102            5 :     }
     103            0 :     return "unknown";
     104              : #elif defined(__APPLE__)
     105              :     char   buf[256];
     106              :     size_t sz = sizeof(buf);
     107              :     if (sysctlbyname("machdep.cpu.brand_string", &buf, &sz, nullptr, 0) == 0) {
     108              :         return std::string(buf, strnlen(buf, sizeof(buf)));
     109              :     }
     110              :     return "unknown";
     111              : #else
     112              :     return "unknown";
     113              : #endif
     114            1 : }
     115              : 
     116            1 : static unsigned long long total_ram_bytes() {
     117              : #ifdef __linux__
     118            1 :     std::ifstream      in("/proc/meminfo");
     119            1 :     std::string        key, unit;
     120            1 :     unsigned long long val = 0ULL;
     121            1 :     while (in >> key >> val >> unit) {
     122            1 :         if (key == "MemTotal:") {
     123            1 :             if (unit == "kB")
     124            1 :                 return val * 1024ULL;
     125            0 :             return val;
     126              :         }
     127              :     }
     128            0 :     return 0ULL;
     129              : #elif defined(__APPLE__)
     130              :     uint64_t mem = 0;
     131              :     size_t   sz  = sizeof(mem);
     132              :     if (sysctlbyname("hw.memsize", &mem, &sz, nullptr, 0) == 0)
     133              :         return static_cast<unsigned long long>(mem);
     134              :     return 0ULL;
     135              : #else
     136              :     return 0ULL;
     137              : #endif
     138            1 : }
     139              : 
     140            1 : static std::string kernel_version() {
     141              : #if defined(__unix__) || defined(__APPLE__)
     142            1 :     struct utsname u{};
     143            1 :     if (uname(&u) == 0) {
     144            1 :         std::ostringstream oss;
     145            1 :         oss << u.sysname << " " << u.release;
     146            1 :         return oss.str();
     147            1 :     }
     148            0 :     return "unknown";
     149              : #else
     150              :     return "unknown";
     151              : #endif
     152              : }
     153              : 
     154            1 : static const char* cpp_standard_str() {
     155              : #if __cplusplus >= 202302L
     156              :     return "C++23+";
     157              : #elif __cplusplus >= 202002L
     158              :     return "C++20";
     159              : #elif __cplusplus >= 201703L
     160            1 :     return "C++17";
     161              : #elif __cplusplus >= 201402L
     162              :     return "C++14";
     163              : #elif __cplusplus >= 201103L
     164              :     return "C++11";
     165              : #else
     166              :     return "pre-C++11";
     167              : #endif
     168              : }
     169              : 
     170            1 : static const char* build_type_str() {
     171              : #ifdef HASHBROWNS_BUILD_TYPE
     172            1 :     return HASHBROWNS_BUILD_TYPE;
     173              : #else
     174              :     return "Unknown";
     175              : #endif
     176              : }
     177              : 
     178              : // ---------------------------------------------------------------------------
     179              : // Pattern / HashStrategy -> string helpers (file-local)
     180              : // ---------------------------------------------------------------------------
     181              : 
     182            4 : static const char* to_string(BenchmarkConfig::Pattern p) {
     183              :     using P = BenchmarkConfig::Pattern;
     184            4 :     switch (p) {
     185            4 :     case P::SEQUENTIAL:
     186            4 :         return "sequential";
     187            0 :     case P::RANDOM:
     188            0 :         return "random";
     189            0 :     case P::MIXED:
     190            0 :         return "mixed";
     191              :     }
     192            0 :     return "sequential";
     193              : }
     194              : 
     195            1 : static const char* to_string(HashStrategy s) {
     196            1 :     switch (s) {
     197            1 :     case HashStrategy::OPEN_ADDRESSING:
     198            1 :         return "open";
     199            0 :     case HashStrategy::SEPARATE_CHAINING:
     200            0 :         return "chain";
     201              :     }
     202            0 :     return "open";
     203              : }
     204              : 
     205              : // ---------------------------------------------------------------------------
     206              : // write_results_csv  (file-local helper called from BenchmarkSuite::run)
     207              : // ---------------------------------------------------------------------------
     208              : 
     209            1 : void write_results_csv_impl(const std::string& path, const std::vector<BenchmarkResult>& results,
     210              :                             [[maybe_unused]] const BenchmarkConfig& cfg, unsigned long long actual_seed) {
     211            1 :     std::ofstream out(path);
     212            1 :     if (!out)
     213            0 :         return;
     214              :     out << "structure,seed,insert_ms_mean,insert_ms_stddev,insert_ms_median,insert_ms_p95,insert_ci_low,insert_ci_"
     215            1 :            "high,";
     216            1 :     out << "search_ms_mean,search_ms_stddev,search_ms_median,search_ms_p95,search_ci_low,search_ci_high,";
     217            1 :     out << "remove_ms_mean,remove_ms_stddev,remove_ms_median,remove_ms_p95,remove_ci_low,remove_ci_high,";
     218              :     out << "memory_bytes,memory_insert_mean,memory_insert_stddev,memory_search_mean,memory_search_stddev,memory_remove_"
     219            1 :            "mean,memory_remove_stddev,";
     220              :     out << "insert_probes_mean,insert_probes_stddev,search_probes_mean,search_probes_stddev,remove_probes_mean,remove_"
     221            1 :            "probes_stddev\n";
     222            2 :     for (const auto& r : results) {
     223            1 :         out << r.structure << "," << actual_seed << "," << r.insert_ms_mean << "," << r.insert_ms_stddev << ","
     224            1 :             << r.insert_ms_median << "," << r.insert_ms_p95 << "," << r.insert_ci_low << "," << r.insert_ci_high << ","
     225            1 :             << r.search_ms_mean << "," << r.search_ms_stddev << "," << r.search_ms_median << "," << r.search_ms_p95
     226            1 :             << "," << r.search_ci_low << "," << r.search_ci_high << "," << r.remove_ms_mean << "," << r.remove_ms_stddev
     227            1 :             << "," << r.remove_ms_median << "," << r.remove_ms_p95 << "," << r.remove_ci_low << "," << r.remove_ci_high
     228            1 :             << "," << r.memory_bytes << "," << r.memory_insert_bytes_mean << "," << r.memory_insert_bytes_stddev << ","
     229            1 :             << r.memory_search_bytes_mean << "," << r.memory_search_bytes_stddev << "," << r.memory_remove_bytes_mean
     230            1 :             << "," << r.memory_remove_bytes_stddev << "," << r.insert_probes_mean << "," << r.insert_probes_stddev
     231            1 :             << "," << r.search_probes_mean << "," << r.search_probes_stddev << "," << r.remove_probes_mean << ","
     232            1 :             << r.remove_probes_stddev << "\n";
     233              :     }
     234            1 : }
     235              : 
     236              : // ---------------------------------------------------------------------------
     237              : // write_results_json  (file-local helper called from BenchmarkSuite::run)
     238              : // ---------------------------------------------------------------------------
     239              : 
     240            1 : void write_results_json_impl(const std::string& path, const std::vector<BenchmarkResult>& results,
     241              :                              const BenchmarkConfig& config, unsigned long long actual_seed) {
     242            1 :     std::ofstream out(path);
     243            1 :     if (!out)
     244            0 :         return;
     245            1 :     out << "{\n";
     246            1 :     out << "  \"meta\": {\n";
     247            1 :     out << "    \"schema_version\": 1,\n";
     248            1 :     out << "    \"size\": " << config.size << ",\n";
     249            1 :     out << "    \"runs\": " << config.runs << ",\n";
     250            1 :     out << "    \"warmup_runs\": " << config.warmup_runs << ",\n";
     251            1 :     out << "    \"bootstrap_iters\": " << config.bootstrap_iters << ",\n";
     252            1 :     out << "    \"structures\": [";
     253            2 :     for (std::size_t i = 0; i < config.structures.size(); ++i) {
     254            1 :         out << "\"" << config.structures[i] << "\"";
     255            1 :         if (i + 1 < config.structures.size())
     256            0 :             out << ",";
     257              :     }
     258            1 :     out << "],\n";
     259            1 :     out << "    \"pattern\": \"" << to_string(config.pattern) << "\",\n";
     260            1 :     out << "    \"seed\": " << actual_seed << ",\n";
     261            1 :     out << "    \"timestamp\": \"";
     262              :     {
     263            1 :         std::time_t t = std::time(nullptr);
     264            1 :         char        buf[64];
     265            1 :         std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&t));
     266            1 :         out << buf;
     267              :     }
     268            1 :     out << "\",\n";
     269            1 :     out << "    \"cpu_governor\": \"" << read_cpu_governor() << "\",\n";
     270            1 :     out << "    \"git_commit\": \"" << git_commit_sha() << "\",\n";
     271            2 :     auto compiler_version = []() -> std::string {
     272              : #if defined(__clang__)
     273              :         return std::string("Clang ") + __clang_version__;
     274              : #elif defined(_MSC_VER)
     275              :         return std::string("MSVC ") + std::to_string(_MSC_FULL_VER);
     276              : #elif defined(__GNUC__)
     277            3 :         return std::string("GCC ") + __VERSION__;
     278              : #else
     279              :         return std::string("unknown");
     280              : #endif
     281            1 :     }();
     282            1 :     out << "    \"compiler\": \"" << compiler_version << "\",\n";
     283            1 :     out << "    \"cpp_standard\": \"" << cpp_standard_str() << "\",\n";
     284            1 :     out << "    \"build_type\": \"" << build_type_str() << "\",\n";
     285            1 :     out << "    \"cpu_model\": \"" << cpu_model() << "\",\n";
     286            1 :     out << "    \"profile\": \"" << config.profile_name << "\",\n";
     287            1 :     out << "    \"cores\": " << std::thread::hardware_concurrency() << ",\n";
     288            1 :     out << "    \"total_ram_bytes\": " << total_ram_bytes() << ",\n";
     289            1 :     out << "    \"kernel\": \"" << kernel_version() << "\",\n";
     290            1 :     out << "    \"hash_strategy\": \"" << to_string(config.hash_strategy) << "\"";
     291            1 :     if (config.hash_initial_capacity)
     292            0 :         out << ",\n    \"hash_capacity\": " << *config.hash_initial_capacity;
     293            1 :     if (config.hash_max_load_factor)
     294            0 :         out << ",\n    \"hash_load\": " << *config.hash_max_load_factor;
     295            1 :     out << ",\n    \"pinned_cpu\": " << (config.pin_cpu ? config.pin_cpu_index : -1);
     296            1 :     out << ",\n    \"turbo_disabled\": " << (config.disable_turbo ? 1 : 0) << ",\n";
     297            1 :     write_profile_manifest(out, config);
     298            1 :     out << "\n  },\n";
     299            1 :     out << "  \"results\": [\n";
     300            2 :     for (std::size_t i = 0; i < results.size(); ++i) {
     301            1 :         const auto& r = results[i];
     302              :         out << "    {"
     303            1 :             << "\"structure\": \"" << r.structure << "\","
     304            2 :             << "\"insert_ms_mean\": " << r.insert_ms_mean << ","
     305            1 :             << "\"insert_ms_stddev\": " << r.insert_ms_stddev << ","
     306            1 :             << "\"insert_ms_median\": " << r.insert_ms_median << ","
     307            1 :             << "\"insert_ms_p95\": " << r.insert_ms_p95 << ","
     308            1 :             << "\"insert_ci_low\": " << r.insert_ci_low << ","
     309            1 :             << "\"insert_ci_high\": " << r.insert_ci_high << ","
     310            1 :             << "\"search_ms_mean\": " << r.search_ms_mean << ","
     311            1 :             << "\"search_ms_stddev\": " << r.search_ms_stddev << ","
     312            1 :             << "\"search_ms_median\": " << r.search_ms_median << ","
     313            1 :             << "\"search_ms_p95\": " << r.search_ms_p95 << ","
     314            1 :             << "\"search_ci_low\": " << r.search_ci_low << ","
     315            1 :             << "\"search_ci_high\": " << r.search_ci_high << ","
     316            1 :             << "\"remove_ms_mean\": " << r.remove_ms_mean << ","
     317            1 :             << "\"remove_ms_stddev\": " << r.remove_ms_stddev << ","
     318            1 :             << "\"remove_ms_median\": " << r.remove_ms_median << ","
     319            1 :             << "\"remove_ms_p95\": " << r.remove_ms_p95 << ","
     320            1 :             << "\"remove_ci_low\": " << r.remove_ci_low << ","
     321            1 :             << "\"remove_ci_high\": " << r.remove_ci_high << ","
     322            1 :             << "\"memory_bytes\": " << r.memory_bytes << ","
     323            1 :             << "\"memory_insert_mean\": " << r.memory_insert_bytes_mean << ","
     324            1 :             << "\"memory_insert_stddev\": " << r.memory_insert_bytes_stddev << ","
     325            1 :             << "\"memory_search_mean\": " << r.memory_search_bytes_mean << ","
     326            1 :             << "\"memory_search_stddev\": " << r.memory_search_bytes_stddev << ","
     327            1 :             << "\"memory_remove_mean\": " << r.memory_remove_bytes_mean << ","
     328            1 :             << "\"memory_remove_stddev\": " << r.memory_remove_bytes_stddev << ","
     329            1 :             << "\"insert_probes_mean\": " << r.insert_probes_mean << ","
     330            1 :             << "\"insert_probes_stddev\": " << r.insert_probes_stddev << ","
     331            1 :             << "\"search_probes_mean\": " << r.search_probes_mean << ","
     332            1 :             << "\"search_probes_stddev\": " << r.search_probes_stddev << ","
     333            1 :             << "\"remove_probes_mean\": " << r.remove_probes_mean << ","
     334            2 :             << "\"remove_probes_stddev\": " << r.remove_probes_stddev << "}" << (i + 1 < results.size() ? "," : "")
     335            2 :             << "\n";
     336              :     }
     337            1 :     out << "  ]\n}\n";
     338            1 : }
     339              : 
     340              : // ---------------------------------------------------------------------------
     341              : // BenchmarkSuite public serialization methods
     342              : // ---------------------------------------------------------------------------
     343              : 
     344            1 : void BenchmarkSuite::write_crossover_csv(const std::string& path, const std::vector<CrossoverInfo>& info) {
     345            1 :     std::ofstream out(path);
     346            1 :     if (!out)
     347            0 :         return;
     348            1 :     out << "operation,a,b,size_at_crossover\n";
     349            4 :     for (const auto& c : info) {
     350            3 :         out << c.operation << "," << c.a << "," << c.b << "," << c.size_at_crossover << "\n";
     351              :     }
     352            1 : }
     353              : 
     354            1 : void BenchmarkSuite::write_crossover_json(const std::string& path, const std::vector<CrossoverInfo>& info,
     355              :                                           const BenchmarkConfig& config) {
     356            1 :     std::ofstream out(path);
     357            1 :     if (!out)
     358            0 :         return;
     359            1 :     out << "{\n";
     360            1 :     out << "  \"meta\": {\n";
     361            1 :     out << "    \"schema_version\": 1,\n";
     362            1 :     out << "    \"runs\": " << config.runs << ",\n";
     363            1 :     out << "    \"profile\": \"" << config.profile_name << "\",\n";
     364            1 :     out << "    \"structures\": [";
     365            3 :     for (std::size_t i = 0; i < config.structures.size(); ++i) {
     366            2 :         out << "\"" << config.structures[i] << "\"";
     367            2 :         if (i + 1 < config.structures.size())
     368            1 :             out << ",";
     369              :     }
     370            1 :     out << "],\n";
     371            1 :     out << "    \"pattern\": \"" << to_string(config.pattern) << "\"";
     372            1 :     if (config.seed.has_value())
     373            0 :         out << ",\n    \"seed\": " << *config.seed;
     374            1 :     out << ",\n";
     375            1 :     write_profile_manifest(out, config);
     376            1 :     out << "\n  },\n";
     377            1 :     out << "  \"crossovers\": [\n";
     378            4 :     for (std::size_t i = 0; i < info.size(); ++i) {
     379            3 :         const auto& c = info[i];
     380              :         out << "    {"
     381            3 :             << "\"operation\": \"" << c.operation << "\","
     382            3 :             << "\"a\": \"" << c.a << "\","
     383            3 :             << "\"b\": \"" << c.b << "\","
     384           12 :             << "\"size_at_crossover\": " << c.size_at_crossover << "}" << (i + 1 < info.size() ? "," : "") << "\n";
     385              :     }
     386            1 :     out << "  ]\n}\n";
     387            1 : }
     388              : 
     389            1 : void BenchmarkSuite::write_series_csv(const std::string& path, const Series& series) {
     390            1 :     std::ofstream out(path);
     391            1 :     if (!out)
     392            0 :         return;
     393            1 :     out << "size,structure,insert_ms,search_ms,remove_ms\n";
     394            5 :     for (const auto& p : series) {
     395            4 :         out << p.size << "," << p.structure << "," << p.insert_ms << "," << p.search_ms << "," << p.remove_ms << "\n";
     396              :     }
     397            1 : }
     398              : 
     399            2 : void BenchmarkSuite::write_series_json(const std::string& path, const Series& series, const BenchmarkConfig& config) {
     400            2 :     std::ofstream out(path);
     401            2 :     if (!out)
     402            0 :         return;
     403            2 :     out << "{\n";
     404            2 :     out << "  \"meta\": {\n";
     405            2 :     out << "    \"schema_version\": 1,\n";
     406            2 :     out << "    \"runs_per_size\": " << config.runs << ",\n";
     407            2 :     out << "    \"profile\": \"" << config.profile_name << "\",\n";
     408            2 :     out << "    \"structures\": [";
     409            6 :     for (std::size_t i = 0; i < config.structures.size(); ++i) {
     410            4 :         out << "\"" << config.structures[i] << "\"";
     411            4 :         if (i + 1 < config.structures.size())
     412            2 :             out << ",";
     413              :     }
     414            2 :     out << "],\n";
     415            2 :     out << "    \"pattern\": \"" << to_string(config.pattern) << "\"";
     416            2 :     if (config.seed.has_value())
     417            0 :         out << ",\n    \"seed\": " << *config.seed;
     418            2 :     out << ",\n";
     419            2 :     write_profile_manifest(out, config);
     420            2 :     out << "\n  },\n";
     421            2 :     out << "  \"series\": [\n";
     422           10 :     for (std::size_t i = 0; i < series.size(); ++i) {
     423            8 :         const auto& p = series[i];
     424           16 :         out << "    {\"size\": " << p.size << ", \"structure\": \"" << p.structure << "\""
     425           16 :             << ", \"insert_ms\": " << p.insert_ms << ", \"search_ms\": " << p.search_ms
     426            8 :             << ", \"remove_ms\": " << p.remove_ms << "}" << (i + 1 < series.size() ? "," : "") << "\n";
     427              :     }
     428            2 :     out << "  ]\n}\n";
     429            2 : }
     430              : 
     431              : } // namespace hashbrowns
        

Generated by: LCOV version 2.0-1