b15106fc09
- CMakeLists.txt - app.md - appicon.ico - main.cpp - perf_tests.cpp - perf_tests.h - qa_panel.cpp - qa_panel.h - qa_state.cpp - qa_state.h - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
190 lines
6.6 KiB
C++
190 lines
6.6 KiB
C++
// perf_tests — stress data_table con N filas (default 1M).
|
|
//
|
|
// Patron: el boton "Performance Tests" del QA panel llama run_perf_test(rows, frames),
|
|
// que prepara el backing storage sync (lento si N grande) y marca el state como
|
|
// "perf mode ON". Durante los siguientes `frames` renders, render_perf_tab()
|
|
// mide latency por frame y al terminar deja resultados en qa::counters().
|
|
#include "perf_tests.h"
|
|
#include "qa_state.h"
|
|
#include "data_table/data_table.h"
|
|
#include "core/logger.h"
|
|
|
|
#include <imgui.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace tables_qa {
|
|
|
|
namespace {
|
|
|
|
// Estado estatico del perf test. Sobrevive entre frames hasta nueva run.
|
|
struct PerfState {
|
|
bool active = false;
|
|
long long rows = 0;
|
|
int frames_target = 0;
|
|
int frames_measured = 0;
|
|
std::vector<double> frame_times_ms;
|
|
std::vector<std::string> back; // backing — puede ser N millones
|
|
std::vector<const char*> ptrs;
|
|
data_table::State dt;
|
|
};
|
|
|
|
PerfState& perf() { static PerfState s; return s; }
|
|
|
|
// Generador deterministico — mismo seed produce mismas filas.
|
|
void seed_synthetic(long long rows) {
|
|
auto& p = perf();
|
|
p.back.clear();
|
|
// Para N filas con 6 columnas, necesitamos rows*6 strings.
|
|
// Reservamos para evitar reallocs durante el push_back.
|
|
p.back.reserve(static_cast<size_t>(rows * 6));
|
|
|
|
char buf[64];
|
|
const char* statuses[] = {"ok", "error", "running", "pending"};
|
|
const char* langs[] = {"go", "py", "cpp"};
|
|
const int n_statuses = 4;
|
|
const int n_langs = 3;
|
|
|
|
for (long long i = 0; i < rows; ++i) {
|
|
// id
|
|
std::snprintf(buf, sizeof(buf), "%lld", i + 1);
|
|
p.back.emplace_back(buf);
|
|
// name
|
|
std::snprintf(buf, sizeof(buf), "item_%lld", i);
|
|
p.back.emplace_back(buf);
|
|
// status
|
|
p.back.emplace_back(statuses[i % n_statuses]);
|
|
// duration_ms — varia 50..7000
|
|
std::snprintf(buf, sizeof(buf), "%lld", 50 + (i * 137) % 6950);
|
|
p.back.emplace_back(buf);
|
|
// lang
|
|
p.back.emplace_back(langs[i % n_langs]);
|
|
// value 0..10000
|
|
std::snprintf(buf, sizeof(buf), "%lld", (i * 71) % 10000);
|
|
p.back.emplace_back(buf);
|
|
}
|
|
|
|
qa::TabState dummy;
|
|
p.ptrs.clear();
|
|
p.ptrs.reserve(p.back.size());
|
|
for (auto& s : p.back) p.ptrs.push_back(s.c_str());
|
|
}
|
|
|
|
} // anon
|
|
|
|
void run_perf_test(long long rows, int frames) {
|
|
fn_log::log_info("perf_test: starting rows=%lld frames=%d", rows, frames);
|
|
auto& p = perf();
|
|
p.active = true;
|
|
p.rows = rows;
|
|
p.frames_target = frames;
|
|
p.frames_measured = 0;
|
|
p.frame_times_ms.clear();
|
|
p.frame_times_ms.reserve(static_cast<size_t>(frames));
|
|
|
|
// Seed sync. Para 10M filas tarda varios segundos.
|
|
auto t0 = std::chrono::steady_clock::now();
|
|
seed_synthetic(rows);
|
|
auto t1 = std::chrono::steady_clock::now();
|
|
double seed_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
|
|
|
|
auto& c = qa::counters();
|
|
c.last_perf_rows = rows;
|
|
c.last_perf_seed_ms = seed_ms;
|
|
c.last_perf_frames = 0;
|
|
c.last_perf_render_ms_p50 = 0.0;
|
|
c.last_perf_render_ms_p95 = 0.0;
|
|
|
|
fn_log::log_info("perf_test: seeded rows=%lld in %.2f ms", rows, seed_ms);
|
|
}
|
|
|
|
void render_perf_tab() {
|
|
auto& p = perf();
|
|
|
|
if (!p.active) {
|
|
ImGui::TextDisabled("No perf test active.");
|
|
ImGui::TextDisabled("Open QA Control Panel and click " "Performance Tests" " to start.");
|
|
return;
|
|
}
|
|
|
|
// KPI header
|
|
auto& c = qa::counters();
|
|
ImGui::Text("Active perf: rows=%lld seed=%.1fms frames_done=%d/%d",
|
|
p.rows, c.last_perf_seed_ms, p.frames_measured, p.frames_target);
|
|
if (p.frames_measured > 0) {
|
|
ImGui::SameLine();
|
|
ImGui::Text(" p50=%.2fms p95=%.2fms",
|
|
c.last_perf_render_ms_p50, c.last_perf_render_ms_p95);
|
|
}
|
|
ImGui::Separator();
|
|
|
|
// Construir TableInput
|
|
data_table::TableInput tbl;
|
|
tbl.name = "perf";
|
|
tbl.headers = {"id", "name", "status", "duration_ms", "lang", "value"};
|
|
tbl.types = {
|
|
data_table::ColumnType::Int,
|
|
data_table::ColumnType::String,
|
|
data_table::ColumnType::String,
|
|
data_table::ColumnType::Float,
|
|
data_table::ColumnType::String,
|
|
data_table::ColumnType::Float,
|
|
};
|
|
tbl.cells = p.ptrs.data();
|
|
tbl.rows = static_cast<int>(p.rows);
|
|
tbl.cols = 6;
|
|
|
|
// Renderers — ejercemos los caros (CategoricalChip + ColorScale + Duration).
|
|
tbl.column_specs.resize(tbl.cols);
|
|
for (int i = 0; i < tbl.cols; i++) tbl.column_specs[i].id = tbl.headers[i];
|
|
tbl.column_specs[2].renderer = data_table::CellRenderer::CategoricalChip;
|
|
tbl.column_specs[2].chips = {
|
|
{"ok", "#22c55e"}, {"error", "#ef4444"},
|
|
{"running", "#f59e0b"}, {"pending", "#a3a3a3"},
|
|
};
|
|
tbl.column_specs[3].renderer = data_table::CellRenderer::Duration;
|
|
tbl.column_specs[3].duration_warn_ms = 1000.0f;
|
|
tbl.column_specs[3].duration_error_ms = 5000.0f;
|
|
tbl.column_specs[4].renderer = data_table::CellRenderer::CategoricalChip;
|
|
tbl.column_specs[4].chips = {
|
|
{"go", "#3b82f6"}, {"py", "#22c55e"}, {"cpp", "#a855f7"},
|
|
};
|
|
tbl.column_specs[5].renderer = data_table::CellRenderer::ColorScale;
|
|
tbl.column_specs[5].range_min = 0.0;
|
|
tbl.column_specs[5].range_max = 10000.0;
|
|
tbl.column_specs[5].range_alpha = 0.30f;
|
|
|
|
// Medir frame time
|
|
ImGui::BeginChild("##perf_host", ImVec2(-1, -1));
|
|
auto t0 = std::chrono::steady_clock::now();
|
|
data_table::render("##perf_tbl", {tbl}, p.dt, nullptr, true);
|
|
auto t1 = std::chrono::steady_clock::now();
|
|
ImGui::EndChild();
|
|
|
|
if (p.frames_measured < p.frames_target) {
|
|
double ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
|
|
p.frame_times_ms.push_back(ms);
|
|
p.frames_measured++;
|
|
|
|
if (p.frames_measured == p.frames_target) {
|
|
// Compute p50 / p95
|
|
std::vector<double> sorted = p.frame_times_ms;
|
|
std::sort(sorted.begin(), sorted.end());
|
|
size_t n = sorted.size();
|
|
double p50 = sorted[n / 2];
|
|
double p95 = sorted[(n * 95) / 100];
|
|
c.last_perf_render_ms_p50 = p50;
|
|
c.last_perf_render_ms_p95 = p95;
|
|
c.last_perf_frames = p.frames_measured;
|
|
fn_log::log_info("perf_test DONE rows=%lld frames=%d p50=%.2fms p95=%.2fms",
|
|
p.rows, p.frames_measured, p50, p95);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace tables_qa
|