164 lines
5.8 KiB
C++
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;
|
|
}
|