Files

164 lines
5.8 KiB
C++

// data_table_bench — headless performance gate for data_table::render().
// Issue 0133: 10M-row bench for the upcoming data_table refactor.
//
// Usage:
// ./data_table_bench --rows 10000000 --duration 30
// ./data_table_bench --rows 100000 --duration 5 # CI smoke (fast)
// ./data_table_bench --rows 1000000 --duration 10 --no-db
//
// Output: JSON to stdout.
// Exit 0 if all scenarios pass (fps_p1 >= threshold), 1 otherwise.
//
// Headless strategy:
// fn::run_app creates the GL context (needed for ImGui clipper to work
// on 10M-row virtual scroll). We hide the GLFW window via glfwHideWindow()
// on the first frame. The Runner::tick() is called each frame to drive the
// benchmark state machine across all 4 scenarios. When tick() returns true
// (all done) we close the window and fn::run_app exits.
#include "app_base.h"
#include "core/logger.h"
#include "bench_runner.h"
#include <imgui.h>
#include <GLFW/glfw3.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
// ---------------------------------------------------------------------------
// Argument parsing
// ---------------------------------------------------------------------------
static void print_usage() {
fprintf(stderr,
"Usage: data_table_bench [options]\n"
" --rows N Number of rows (default: 10000000)\n"
" --duration S Seconds per scenario (default: 30)\n"
" --threshold F fps_p1 pass threshold (default: 60.0)\n"
" --db PATH Persist results to SQLite (default: operations.db)\n"
" --no-db Skip persistence\n"
" --sha SHA Git commit SHA to tag run\n"
" --verbose Extra logging\n"
);
}
struct Args {
long long rows = 10000000;
double duration_s = 30.0;
double threshold = 60.0;
std::string db_path = "operations.db";
std::string commit_sha;
bool verbose = false;
};
static Args parse_args(int argc, char** argv) {
Args a;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--rows") == 0 && i + 1 < argc)
a.rows = atoll(argv[++i]);
else if (strcmp(argv[i], "--duration") == 0 && i + 1 < argc)
a.duration_s = atof(argv[++i]);
else if (strcmp(argv[i], "--threshold") == 0 && i + 1 < argc)
a.threshold = atof(argv[++i]);
else if (strcmp(argv[i], "--db") == 0 && i + 1 < argc)
a.db_path = argv[++i];
else if (strcmp(argv[i], "--no-db") == 0)
a.db_path = "";
else if (strcmp(argv[i], "--sha") == 0 && i + 1 < argc)
a.commit_sha = argv[++i];
else if (strcmp(argv[i], "--verbose") == 0)
a.verbose = true;
else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_usage();
exit(0);
}
}
return a;
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(int argc, char** argv) {
Args args = parse_args(argc, argv);
fprintf(stderr, "[bench] rows=%lld duration=%.0fs threshold=%.0f db=%s\n",
args.rows, args.duration_s, args.threshold,
args.db_path.empty() ? "(none)" : args.db_path.c_str());
bench::Config cfg;
cfg.rows = args.rows;
cfg.duration_s = args.duration_s;
cfg.fps_threshold = args.threshold;
cfg.db_path = args.db_path;
cfg.commit_sha = args.commit_sha;
cfg.verbose = args.verbose;
// Seed dataset before opening GL context — can take several seconds for 10M.
fprintf(stderr, "[bench] seeding %lld x %d dataset... ", cfg.rows, cfg.cols);
fflush(stderr);
bench::Runner runner(cfg);
fprintf(stderr, "done\n");
fflush(stderr);
// fn::run_app config — headless visual setup.
fn::AppConfig app_cfg;
app_cfg.title = "data_table_bench";
app_cfg.width = 1920;
app_cfg.height = 1080;
app_cfg.vsync = false; // uncapped FPS for meaningful bench
app_cfg.viewports = false; // no floating panels
app_cfg.auto_dockspace = false; // no dockspace overhead
app_cfg.auto_layouts = false; // no layout DB
app_cfg.about.name = "data_table_bench";
app_cfg.about.version = "0.1.0";
app_cfg.about.description = "Headless 10M-row bench for data_table (issue 0133)";
// Do NOT set app_cfg.log — logger prints to stdout, polluting JSON output.
app_cfg.header_badge.enabled = false; // headless — no badge
static GLFWwindow* g_window = nullptr;
bool window_hidden = false;
int rc = fn::run_app(app_cfg, [&]() {
// First frame: grab window handle and hide.
if (!g_window) {
ImGuiViewport* vp = ImGui::GetMainViewport();
if (vp && vp->PlatformHandle) {
g_window = (GLFWwindow*)vp->PlatformHandle;
glfwHideWindow(g_window);
window_hidden = true;
}
}
// tick() drives the benchmark state machine, renders the table,
// and returns true when all 4 scenarios are complete.
bool finished = runner.tick();
if (finished && g_window) {
glfwSetWindowShouldClose(g_window, GLFW_TRUE);
}
});
if (rc != 0) {
fprintf(stderr, "[bench] run_app error: %d\n", rc);
return 1;
}
// Print JSON to stdout.
runner.print_json();
// Persist to SQLite.
if (!args.db_path.empty()) {
runner.persist(args.db_path);
fprintf(stderr, "[bench] results persisted to %s\n", args.db_path.c_str());
}
bool passed = runner.all_passed();
fprintf(stderr, "[bench] overall: %s\n", passed ? "PASS" : "FAIL");
return passed ? 0 : 1;
}