diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 98e26cfc..e01c1b64 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -543,7 +543,13 @@ if(EXISTS ${_KANBAN_CPP_DIR}/CMakeLists.txt) endif() # --- data_table_bench (lives in apps/, issue 0133) --- +# Requires SQLite3 dev libs. Skip silently when not available (e.g. cross-windows build). set(_DATA_TABLE_BENCH_DIR ${CMAKE_SOURCE_DIR}/../apps/data_table_bench) if(EXISTS ${_DATA_TABLE_BENCH_DIR}/CMakeLists.txt) - add_subdirectory(${_DATA_TABLE_BENCH_DIR} ${CMAKE_BINARY_DIR}/apps/data_table_bench) + find_package(SQLite3 QUIET) + if(SQLite3_FOUND) + add_subdirectory(${_DATA_TABLE_BENCH_DIR} ${CMAKE_BINARY_DIR}/apps/data_table_bench) + else() + message(STATUS "Skipping data_table_bench (SQLite3 dev libs not found)") + endif() endif() diff --git a/dev/issues/0133-cpp-data-table-10m-rows.md b/dev/issues/0133-cpp-data-table-10m-rows.md new file mode 100644 index 00000000..478a5e13 --- /dev/null +++ b/dev/issues/0133-cpp-data-table-10m-rows.md @@ -0,0 +1,74 @@ +--- +id: "0133" +title: "data_table: optimizar para 10M filas sin caida de FPS (finalize modulo)" +status: pendiente +type: refactor +domain: + - cpp-stack + - data-ingest +scope: app-scoped +priority: alta +depends: [] +blocks: [] +related: + - "0081" + - "0097" +created: 2026-05-22 +updated: 2026-05-22 +tags: [cpp, imgui, performance, data_table, finalize] +flow: "" +--- + +# 0133 — data_table 10M rows sin caida FPS + +**Status:** pendiente + +## Por que + +`data_table_cpp_viz` (modulo `fn_module_data_table` / `fn_table_viz`) actualmente maneja decenas de miles de filas con `ImGuiListClipper` y rinde bien. Apps reales (call_monitor con telemetria, services_monitor con escalado, futuro graph_explorer con nodos) ya nos llevan a millones de filas. Objetivo: cerrar el modulo con benchmark estable de **10M filas a >=60fps** en hardware tipico (Ryzen 5 / i5 8th gen + 16GB). + +## Que entrega + +Refactor del modulo manteniendo API publica + un benchmark suite. + +### Cambios tecnicos + +1. **Storage columnar** — hoy `std::vector>` row-major. Cambiar a column-major (`Column { type; vector data }`) para localidad de cache + iteracion. Las celdas se materializan solo para las filas visibles. +2. **String interning** — columnas de tipo string usan tabla de strings global con `uint32_t` indices. 10M filas con 50% strings repetidas → ahorra 60-70% RAM. +3. **Lazy filter/sort indices** — en vez de re-ordenar el storage, mantener `vector visible_rows` que apunta al storage subyacente. Filter/sort solo reescribe ese vector. +4. **Computed columns en bloques** — `compute_stage_cpp_core` ahora corre por cell; cambiar a procesar bloques de 1024 filas con SIMD via `OpenMP` (ya esta linkeado en fn_framework). +5. **Render path** — `ImGuiListClipper` sigue siendo el frontend, pero el callback de render no debe asignar memoria por fila. Pre-formatear strings de display en `column.display_cache[row_idx]` con LRU de 100k entradas; resto se formatea on-the-fly. +6. **Color rules** — `data_table_color_rules_cpp_viz` se evalua hoy por celda visible. Cachear el rule_id resuelto por row_idx tras primer paint. +7. **Stats** — `compute_column_stats_cpp_core` solo se recalcula cuando cambia el filtro, no cada frame. + +### Benchmark suite + +`cpp/apps/data_table_bench/`: +- Genera dataset sintetico 10M filas x 20 cols (mix int/float/string/timestamp). +- Mide FPS sostenido durante: + - scroll lineal full range (down → bottom). + - filter por string match (`LIKE %foo%`). + - sort por columna numerica. + - color rule `value > p95`. +- Output: `fps_p50`, `fps_p1`, `mem_rss_mb`, `cpu_pct`. +- Asercion DoD: `fps_p1 >= 60` en cada escenario. + +## DoD + +- Refactor entregado sin romper apps consumidoras (call_monitor, services_monitor, graph_explorer, navegator_dashboard, kanban_cpp future). +- Benchmark suite ejecutable: `./data_table_bench --rows 10000000 --duration 30`. +- Resultados de benchmark guardados en `apps/data_table_bench/operations.db` con assertion `fps_p1 >= 60`. +- `e2e_checks` corriendo benchmark con dataset reducido (100k filas) en CI; full bench manual. +- Modulo marcado `version: 1.0.0` y `tags: [stable]` en su `.md`. +- Guia "porting old call sites" si la API publica cambia (en `cpp/functions/viz/data_table/MIGRATION.md`). + +## Anti-scope + +- Sin GPU rendering (sigue siendo CPU + ImGui). +- Sin paginacion remota (sigue todo in-memory). +- Sin streaming append-while-rendering (snapshot al frame inicio). +- Sin virtualizacion horizontal (todas las cols se renderizan; assumed N_cols <= 100). + +## Notas + +Issue 0081 introdujo la migracion inline → modulo. Issue 0097 cerro el wrapping en fn_module/fn_table_viz. Esta issue es el **finalize**: lo deja `1.0.0` con benchmark + suficiente performance para que las apps de telemetria/graph no necesiten paginar manual. diff --git a/functions/infra/agent_cleanup_worktree.go b/functions/infra/agent_cleanup_worktree.go index 0caa15d2..23a0027b 100644 --- a/functions/infra/agent_cleanup_worktree.go +++ b/functions/infra/agent_cleanup_worktree.go @@ -1,3 +1,5 @@ +//go:build !windows + package infra import ( diff --git a/modules/data_table/MIGRATION.md b/modules/data_table/MIGRATION.md new file mode 100644 index 00000000..5226f275 --- /dev/null +++ b/modules/data_table/MIGRATION.md @@ -0,0 +1,148 @@ +# data_table MIGRATION guide + +Referencia para apps que migran a v1.0.0 estable del modulo `data_table`. +La version de modulo (`module.md`) es semver independiente del entrypoint (`data_table.md`). +Este documento cubre el salto al hito de estabilidad 1.0.0, no versiones intermedias. + +--- + +## v2.x → estabilidad 1.0.0 (pendiente gate 0133) + +### What changed (internals — no API change) + +Las optimizaciones planificadas en issue 0133 son **transparentes para el caller**. La API publica (`data_table.h`) no cambia. + +| Cambio interno | Impacto en caller | +|---|---| +| Columnar snapshot interno (agente B) | Ninguno. `TableInput.cells` sigue siendo row-major caller-owned. | +| String interning de celdas en snapshot | Ninguno. Misma interfaz de lectura. Strings siguen viviendo en el caller. | +| Lazy `visible_rows` (filter + sort diferidos) | Ninguno. `render()` sigue siendo una sola llamada por frame. | +| Display cache per-cell | Ninguno. La cache es opaca al caller. | +| OpenMP en compute (agente A bench gate) | Ninguno. Threading interno, thread-safety invariante: llamar solo desde el main thread de ImGui. | + +### What you must do + +**Nada**, si usas la API publica. + +- `data_table::render(id, tables, st, events_out, show_chrome)` — firma identica. +- `TableInput`, `State`, `TableEvent`, `ColumnSpec`, `ColorRule` — sin cambios de layout. +- Back-compat overload `render(id, tables, st, show_chrome)` — sigue compilando. + +Casos especificos: + +| Situacion | Accion | +|---|---| +| Guardabas punteros a `TableInput.cells` entre frames | Sigue valido. El caller es dueno de `cells`; el modulo no lo mueve ni libera. | +| Usabas `data_table_internal.h` directamente | Rebuild obligatorio. El header es privado del modulo — si lo incluias, estabas fuera del contrato. No se garantiza estabilidad de `UiState` ni de los helpers internos. | +| Enlazan `fn_table_viz` (target antiguo) | Reemplazar por `fn_module_data_table`. El target `fn_table_viz` fue deprecado en v1.4.0 (2026-05-16). | + +### Behavior contracts preserved + +Estos contratos estan FROZEN en v1.0.0 y no pueden romperse sin major version bump: + +- **Bit-identical rendering**: misma entrada → misma salida visual (excepto antialiasing de ImGui). +- **`TableEvent.row` indexa `TableInput`**: los indices de fila en eventos (`ButtonClick`, `RowDoubleClick`, `RowRightClick`) referencian la tabla de entrada original, no el snapshot interno ni la vista filtrada. +- **`stats_last_cells` pointer-identity sentinel**: el campo `State::stats_last_cells` se usa internamente para detectar cambio de datos. Si el caller pasa el mismo puntero `cells` en frames consecutivos, el modulo reutiliza la cache de stats. Cambiar el puntero (aunque el contenido sea igual) invalida la cache — comportamiento documentado y frozen. +- **`events_out` solo hace `push_back`**: `render()` nunca llama `clear()` ni `resize()` sobre el vector del caller. El caller limpia antes de cada frame si no quiere acumulacion. +- **`show_chrome = true` por defecto**: el overload de back-compat sin `show_chrome` pasa `true`. +- **`State` es caller-managed**: el modulo no alloca ni libera el `State`. El caller lo destruye cuando quiere. + +### New (optional, v1.0.0+) + +*(placeholder — se documentaran aqui las features opt-in que lleguen post-gate)* + +--- + +## Backwards compatibility policy + +La API publica de `data_table::render`, `TableInput`, `State`, `TableEvent`, `ColumnSpec` y `ColorRule` esta **FROZEN** en v1.0.0. + +- **Breaking changes** (cambiar firma, quitar campo, cambiar semántica de parametro existente) requieren major version bump y un periodo de coexistencia con el path anterior. +- **Additive changes** (nuevo campo en struct con default sensato, nuevo overload, nuevo `CellRenderer` enum value) son minor — consumidores existentes no necesitan cambios. +- **Bugfixes** y optimizaciones internas son patch — sin cambio de contrato. + +Apps consumidoras que solo usen `#include "data_table/data_table.h"` y `#include "core/data_table_types.h"` no necesitan cambios en minor y patch bumps. + +--- + +## Porting desde el playground (pre-registry) + +Si tu app usaba el playground original (`cpp/apps/primitives_gallery/playground/tables/data_table.h`): + +1. **Cambiar include path**: + ```cpp + // Antes + #include "tables/data_table.h" + #include "tables/data_table_types.h" + + // Despues + #include "data_table/data_table.h" + #include "core/data_table_types.h" + ``` + +2. **Cambiar target CMake**: + ```cmake + # Antes + target_link_libraries(mi_app PRIVATE fn_table_viz) + + # Despues + target_link_libraries(mi_app PRIVATE fn_module_data_table) + ``` + +3. **`app.md`**: declarar `uses_modules: [data_table_cpp]` en lugar de listar funciones miembro individualmente. + +4. **Namespace identico**: `data_table::render`, `data_table::State`, `data_table::TableInput` — sin cambios. + +5. **`data_table_logic.h` eliminado**: los helpers internos del playground (`row_to_tsv`, drill, view_mode, etc.) eran privados. En el modulo son `static` en `data_table.cpp`. Si los necesitabas externamente, estan fuera del contrato — contactar para evaluar si deben promoverse al registry. + +--- + +## Release checklist (gate 0133 — NO ejecutar hasta A+B listos) + +Pasos exactos para ejecutar cuando agentes A y B completen su trabajo: + +1. **Bench gate**: `data_table_bench --rows 10000000` debe reportar `fps_p1 >= 60`. El agente A construye el bench; esta metrica es el prerequisito de estabilidad. + +2. **fn doctor clean**: `fn doctor cpp-apps` debe pasar sin nuevos `CANDIDATE` (tablas inline sin migrar en apps consumidoras). Indica que todos los consumidores usan el modulo correctamente. + +3. **Build 11 consumidores**: compilar los 11 apps que linkean `fn_module_data_table` sin errores ni warnings nuevos. Verificar con: + ```bash + cd cpp/build && cmake --build . --target \ + registry_dashboard kanban dag_engine_ui services_monitor \ + graph_explorer chart_demo 2>&1 | grep -E "error:|warning:" + ``` + *(ajustar lista de targets segun `fn doctor cpp-apps` output)* + +4. **Version bump**: + ```bash + /version modules/data_table minor "estable 1.0.0 + columnar + 10M rows" + ``` + Esto bumpa el campo `version:` en `module.md` (actualmente `2.1.0`) a `3.0.0` (major bump porque el modulo alcanza estabilidad contractual) o al numero que corresponda segun la politica semver del proyecto en ese momento. + + > Nota: `data_table.md` tiene version `1.5.0` (entrypoint). `module.md` tiene `2.1.0` (modulo). El bump de "estabilidad 1.0.0" es un hito de politica — el numero exacto lo decide el operador segun cual de los dos .md es la fuente de verdad para el semver del modulo. + +5. **Tag stable**: en `module.md` frontmatter, anadir `tags: [stable]` al array existente `[tables, viz, ui, imgui, tql, cpp]`. + +6. **Capability growth log**: descomentar la entrada preparada en `data_table.md` (ver seccion al final del archivo), rellenando la fecha real `YYYY-MM-DD`. + +7. **Push + tag git**: + ```bash + git add modules/data_table/module.md modules/data_table/data_table.md + git commit -m "feat(data_table): stable 1.0.0 — columnar + 10M rows gate passed" + git tag data_table/v1.0.0 + git push && git push --tags + ``` + +--- + +## Inconsistencias detectadas en doc actual + +Las siguientes inconsistencias fueron detectadas durante la preparacion de este documento. No bloquean el gate pero conviene resolver antes del bump: + +1. **Version drift entre los dos .md**: `data_table.md` tiene `version: 1.5.0` y `module.md` tiene `version: 2.1.0`. Son versionados independientemente pero no esta documentado explicitamente cual es la "version publica" del modulo. Recomendacion: clarificar en `module.md` que su version es la del modulo como unidad y `data_table.md` es la del entrypoint como funcion del registry. + +2. **`error_type: "error_go_core"` en `data_table.md`**: la funcion es C++ pura (no retorna `error` de Go). El campo `error_type` del frontmatter parece heredado del template Go. No afecta el comportamiento pero es semanticamente incorrecto para un entrypoint C++. + +3. **`tests` array en `data_table.md` apunta a `cpp/tests/test_column_specs.cpp`** pero la documentacion dice "No hay tests unitarios directos". Los tests listados son del harness de compilacion (`cpp/tests/`), no del entrypoint en si. Aclarar en `## Notas` que el `test_file_path` referencia tests de compilacion/link, no tests de render. + +4. **`llm_anthropic_cpp_core` en `module.md` uses_functions** pero `data_table.md` no lo lista (stub interno). Alinear: si el stub es interno al modulo, deberia estar en `members`, no en `uses_functions`. diff --git a/modules/data_table/data_table.md b/modules/data_table/data_table.md index e32cc282..37fa49e6 100644 --- a/modules/data_table/data_table.md +++ b/modules/data_table/data_table.md @@ -43,7 +43,7 @@ uses_types: - ColorRule_cpp_core returns: [] returns_optional: false -error_type: "error_go_core" +error_type: "" imports: - imgui.h - app_base.h @@ -261,6 +261,10 @@ No hay tests unitarios directos: `render()` requiere ImGui + ImPlot context acti ## Capability growth log + + v1.1.0 (2026-05-15) — declarative CellRenderer (Badge/Progress/Duration/Icon) via TableInput.column_specs sidecar. Back-compat preservado: apps existentes sin column_specs siguen funcionando sin cambios. v1.2.0 (2026-05-15) — Button renderer + event sink (ButtonClick/RowDoubleClick/RowRightClick) + tooltip per cell + column_specs persisted in TQL (aux_column_specs roundtrip). Back-compat preserved: events_out=nullptr by default; existing render() callers unchanged.