// 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 #include #include #include #include #include // --------------------------------------------------------------------------- // 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)"; app_cfg.log = {"data_table_bench.log", 1}; 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; }