blob: c2921cba7969dc8d4e6ab48dbcb3a99766e65890 [file]
/*
* Read-path memory model verification experiment.
*
* Validates the memory formula:
* M_read ≈ M_fixed + batch_size × s_row + N_cols × C_page
*
* Experiments with different column counts and batch sizes,
* recording peak memory per ModStat for each configuration.
*
* Prerequisite: run no_flush_bench (write_memory) first to generate
* experiment.tsfile.
*
* Build:
* cmake -DENABLE_MEM_STAT=ON -DBUILD_TEST=OFF ..
* cmake --build . --target read_mem_model
*/
#include <sys/resource.h>
#include <sys/time.h>
#include <chrono>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include "common/allocator/alloc_base.h"
#include "reader/table_result_set.h"
#include "reader/tsfile_reader.h"
static const char* kTable = "mem_bench";
static const int kModCount = common::__LAST_MOD_ID;
static int64_t tv_to_us(const struct timeval& tv) {
return static_cast<int64_t>(tv.tv_sec) * 1000000 + tv.tv_usec;
}
using steady_clock = std::chrono::steady_clock;
static steady_clock::time_point g_t0;
static void write_csv_header(std::ofstream& csv) {
csv << "n_cols,batch_size,rows_read,phase,wall_us,user_cpu_us,sys_cpu_us";
for (int i = 0; i < kModCount; i++) csv << "," << common::g_mod_names[i];
csv << ",TOTAL\n";
}
static void write_csv_row(std::ofstream& csv, int n_cols, uint32_t batch_size,
int64_t rows, const char* phase) {
int64_t wall_us = std::chrono::duration_cast<std::chrono::microseconds>(
steady_clock::now() - g_t0)
.count();
struct rusage ru;
getrusage(RUSAGE_SELF, &ru);
int64_t user_us = tv_to_us(ru.ru_utime);
int64_t sys_us = tv_to_us(ru.ru_stime);
auto& ms = common::ModStat::get_instance();
csv << n_cols << "," << batch_size << "," << rows << "," << phase << ","
<< wall_us << "," << user_us << "," << sys_us;
int64_t total = 0;
for (int i = 0; i < kModCount; i++) {
int32_t val = ms.get_stat(i);
csv << "," << val;
total += val;
}
csv << "," << total << "\n";
}
static void print_separator(const char* label) {
std::cout << "\n──── " << label << " ";
for (int i = 0; i < 50 - (int)std::string(label).size(); i++)
std::cout << "─";
std::cout << "\n";
}
int main() {
// Path to tsfile generated by no_flush_bench (write_memory experiment).
static const char* baseline_path =
"/Users/colin/dev/tsfile_b1/cpp/experiment/experiment.tsfile";
static const char* csv_prefix = "read_memory_results";
static const int64_t baseline_rows = 200000000;
std::cout << "=== Read Memory Model Experiment ===\n"
<< " baseline: " << baseline_path << "\n"
<< " csv_prefix: " << csv_prefix << "\n\n";
static const int64_t print_interval_rows = 1000000; // sample every 1M rows
storage::libtsfile_init();
g_t0 = steady_clock::now();
// Parameter matrix
int col_counts[] = {1, 2, 4, 6};
uint32_t batch_sizes[] = {1024, 4096, 16384, 65536};
print_separator("BASELINE");
std::cout << "Baseline file: " << baseline_path << "\n";
print_separator("EXPERIMENTS");
std::cout << "Parameter matrix: N_cols=" << col_counts[0];
for (size_t i = 1; i < sizeof(col_counts) / sizeof(col_counts[0]); i++)
std::cout << "/" << col_counts[i];
std::cout << ", batch_size=";
std::cout << batch_sizes[0];
for (size_t i = 1; i < sizeof(batch_sizes) / sizeof(batch_sizes[0]); i++)
std::cout << "/" << batch_sizes[i];
std::cout << "\n\n";
int exp_count = 0;
for (int n_cols : col_counts) {
for (uint32_t batch_size : batch_sizes) {
// Generate CSV filename with parameters
char csv_filename[256];
snprintf(csv_filename, sizeof(csv_filename),
"%s_cols%d_batch%u.csv", csv_prefix, n_cols, batch_size);
std::ofstream csv(csv_filename);
if (!csv.is_open()) {
std::cerr << "cannot open csv: " << csv_filename << "\n";
return 1;
}
write_csv_header(csv);
// Select column names (first n_cols FIELD columns)
static const char* kFieldCols[] = {"s1", "s2", "s3", "s4",
"s5", "s6", "s7", "s8"};
std::vector<std::string> cols = {"id1", "id2"};
for (int c = 0; c < n_cols && c < 8; c++)
cols.push_back(kFieldCols[c]);
// Open reader and query
storage::TsFileReader reader;
std::cout << " [open] cols=" << n_cols << " batch=" << batch_size
<< std::flush;
if (reader.open(baseline_path) != 0) {
std::cerr << "\nFailed to open: " << baseline_path << "\n";
return 1;
}
std::cout << " [query]" << std::flush;
storage::ResultSet* rs = nullptr;
int ret =
reader.query(kTable, cols, 0, baseline_rows, rs, batch_size);
if (ret != 0) {
std::cerr << "\nquery failed: " << ret << "\n";
reader.close();
return 1;
}
if (rs == nullptr) {
std::cerr << "\nresult_set is null after query\n";
reader.close();
return 1;
}
std::cout << " [read]" << std::flush;
common::TsBlock* block = nullptr;
int64_t total_rows_read = 0;
int64_t next_sample_at = print_interval_rows;
write_csv_row(csv, n_cols, batch_size, 0, "before_read");
while (rs->get_next_tsblock(block) == common::E_OK && block) {
total_rows_read += block->get_row_count();
while (total_rows_read >= next_sample_at) {
write_csv_row(csv, n_cols, batch_size, total_rows_read,
"read");
next_sample_at += print_interval_rows;
}
}
write_csv_row(csv, n_cols, batch_size, total_rows_read,
"after_read");
rs->close();
delete rs;
reader.close();
csv.close();
std::cout << " [" << std::setw(2) << (++exp_count) << "] "
<< "cols=" << std::setw(2) << n_cols
<< " batch=" << std::setw(5) << batch_size
<< " rows_read=" << total_rows_read << " -> "
<< csv_filename << "\n";
}
}
print_separator("SUMMARY");
std::cout << "Total experiments: " << exp_count << "\n"
<< "CSV files: " << csv_prefix
<< "_cols{1,4,8,16}_batch{1024,4096,16384,65536}.csv\n";
storage::libtsfile_destroy();
return 0;
}