yams/tests/unit/daemon/tuning_reconciliation_catch2_test.cpp
Trevon 0dd3985db4
refactor: simplify runtime paths and harden retrieval (#61)
* refactor(tuning): prune dormant runtime knobs

Remove unused tuning controls and adaptive search budget state. Extract testable ONNX slot reconciliation and search telemetry hydration seams, and correct the Apple Silicon sysctl value width.

* refactor(vector): retire dormant backends

Remove unused FAISS, model-infrastructure, VectorIndexManager, and TurboQuant paths while keeping Simeon PQ and float-vector storage.

Migrate unused V2.2 quantized schemas transactionally to V2.3. Databases containing non-default quantized formats or packed-code blobs fail closed with recovery guidance and no mutation.

* refactor(plugins): remove legacy SDK tooling

Remove the Ghidra and SDK submodules, their bespoke tests, and stale scan exclusions. Retain generic PluginProcess coverage against the repository mock fixture.

* build(vector): remove stale FAISS overrides

* refactor(ipc): remove dormant FSM snapshots

* refactor(ingest): unify bounded KG enqueue

* fix(plugins): harden allocation contracts

Keep ABI outputs null or fully initialized when allocations fail, and represent empty object payloads without relying on malloc(0).

* fix(storage): define empty object reads

Return a null buffer with zero length for successful empty-object reads instead of treating malloc(0) as an allocation contract.

* refactor(storage): trim through string views

* test: prune dormant harness helpers

Remove unused readiness and fixture helpers, and keep benchmark client-count doubling in a wide integer domain.

* chore(cpp): initialize analyzer-facing locals

* build(simeon): advance analyzer cleanup

* refactor(config): drop dead daemon client keys

* refactor(ipc): inject daemon client transport

Socket and in-process execution now share DaemonClient request dispatch, so CLI fallbacks do not need alternate service paths.

* refactor(retrieval): route CLI through daemon

Search and grep now use the same daemon handlers over socket or in-process transport. Live grep remains an explicit freshness inspection.

* refactor(graph): move CLI scope into daemon

Graph CLI requests now share daemon handlers across socket and in-process transports. CWD filtering occurs before pagination so scoped totals and pages stay correct.

* build: isolate test-only runtime controls

* refactor(metadata): decouple lock telemetry

* fix(services): return on request timeout

* fix(retrieval): enforce workspace scope

* fix(graph): scope nodes before pagination

* fix(grep): drop binary result lines

* fix(daemon): bound checkpoint wait

* fix(repair): use KG health checks

* fix(storage): always verify compressed CRC

* perf(vector): skip unchanged PQ snapshots

* build(zyp): exclude bench from default build

* build: protect Meson files from formatter

* refactor: remove unreachable production APIs

* refactor(daemon): remove dormant runtime state

* fix(vector): harden daemon provider calls

Timed-out coroutines must own their client and requests. Provider discovery must also fail instead of returning synthetic embeddings in production.

* refactor(storage): remove unusable adapters

* fix(cli): unify in-process add requests

* fix(tsan): preserve async worker progress

* test(cli): isolate daemon-dependent suites

* fix(repair): expose queue backoff progress
2026-07-31 14:16:45 -04:00

857 lines
36 KiB
C++

// TuneAdvisor / TuningManager reconciliation + pool config + ETA calculation tests (Catch2)
// Tests three areas:
// Group A: Post-ingest budget reconciliation and distribution correctness
// Group B: Pool configuration minimum guards
// Group C: ETA remaining calculation accuracy
//
// Uses same TDD pattern as tuning_allocation_catch2_test.cpp.
#include <array>
#include <climits>
#include <cstdlib>
#include <string>
#include "../../common/env_compat.h"
#include "../../common/test_helpers_catch2.h"
#include <catch2/catch_test_macros.hpp>
#include <yams/compat/unistd.h>
#include <yams/daemon/components/GradientLimiter.h>
#include <yams/daemon/components/ServiceManager.h>
#include <yams/daemon/components/TuneAdvisor.h>
#include <yams/daemon/components/TuningManager.h>
using namespace yams::daemon;
namespace {
class ProfileGuard {
TuneAdvisor::Profile prev_;
public:
explicit ProfileGuard(TuneAdvisor::Profile profile) : prev_(TuneAdvisor::tuningProfile()) {
TuneAdvisor::setTuningProfile(profile);
}
~ProfileGuard() { TuneAdvisor::setTuningProfile(prev_); }
ProfileGuard(const ProfileGuard&) = delete;
ProfileGuard& operator=(const ProfileGuard&) = delete;
};
using yams::test::ScopedEnvVar;
class HwGuard {
unsigned prev_;
public:
explicit HwGuard(unsigned hw) : prev_(TuneAdvisor::hardwareConcurrency()) {
TuneAdvisor::setHardwareConcurrencyForTests(hw);
}
~HwGuard() { TuneAdvisor::setHardwareConcurrencyForTests(prev_); }
HwGuard(const HwGuard&) = delete;
HwGuard& operator=(const HwGuard&) = delete;
};
/// Reset all per-stage overrides to 0 (no override) and dynamic caps to UINT32_MAX (unset)
void resetPostIngestOverrides() {
TuneAdvisor::setPostExtractionConcurrent(0);
TuneAdvisor::setPostKgConcurrent(0);
TuneAdvisor::setPostSymbolConcurrent(0);
TuneAdvisor::setPostEntityConcurrent(0);
TuneAdvisor::setPostTitleConcurrent(0);
TuneAdvisor::setPostEmbedConcurrent(0);
TuneAdvisor::setPostExtractionConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostKgConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostSymbolConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostEntityConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostTitleConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostEmbedConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostIngestTotalConcurrent(0);
}
void setAllPostIngestStagesActive() {
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Extraction, true);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::KnowledgeGraph, true);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Symbol, true);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Entity, true);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Title, true);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Embed, true);
}
/// Helper: sum all per-stage post-ingest concurrent values
uint32_t sumAllStages() {
return TuneAdvisor::postExtractionConcurrent() + TuneAdvisor::postKgConcurrent() +
TuneAdvisor::postSymbolConcurrent() + TuneAdvisor::postEntityConcurrent() +
TuneAdvisor::postTitleConcurrent() + TuneAdvisor::postEmbedConcurrent();
}
} // namespace
TEST_CASE("ONNX slot budget preserves shared capacity",
"[daemon][tune][onnx][reconciliation][catch2]") {
SECTION("Normal operation expands the pool for all requested reservations") {
const auto budget = computeOnnxSlotBudget(
/*desiredMax=*/4, /*glinerReserved=*/2, /*embedReserved=*/3,
/*rerankerReserved=*/1, /*pressureCap=*/2, /*underPressure=*/false);
CHECK(budget.maxSlots == 7);
CHECK(budget.glinerReserved == 2);
CHECK(budget.embedReserved == 3);
CHECK(budget.rerankerReserved == 1);
}
SECTION("Pressure keeps one shared slot and trims reservations in priority order") {
const auto budget = computeOnnxSlotBudget(
/*desiredMax=*/12, /*glinerReserved=*/2, /*embedReserved=*/4,
/*rerankerReserved=*/3, /*pressureCap=*/5, /*underPressure=*/true);
CHECK(budget.maxSlots == 5);
CHECK(budget.glinerReserved == 2);
CHECK(budget.embedReserved == 2);
CHECK(budget.rerankerReserved == 0);
}
SECTION("Registry bounds hold for extreme inputs") {
const auto budget = computeOnnxSlotBudget(
/*desiredMax=*/100, /*glinerReserved=*/100, /*embedReserved=*/100,
/*rerankerReserved=*/100, /*pressureCap=*/1, /*underPressure=*/true);
CHECK(budget.maxSlots == 2);
CHECK(budget.glinerReserved == 1);
CHECK(budget.embedReserved == 0);
CHECK(budget.rerankerReserved == 0);
}
}
// =============================================================================
// Group A: Post-ingest budget reconciliation and distribution correctness
// =============================================================================
TEST_CASE("Post-ingest budget distribution correctness", "[daemon][tune][reconciliation][catch2]") {
SECTION("All active stages get >= 1 when budget allows") {
// With 6 active stages and budget >= 6, every stage should get at least 1.
resetPostIngestOverrides();
setAllPostIngestStagesActive();
ScopedEnvVar envPostIngest("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
ScopedEnvVar envMaxThreads("YAMS_MAX_THREADS", "0");
for (unsigned hw : {8u, 12u, 16u, 24u, 32u}) {
HwGuard hwGuard(hw);
for (auto profile : {TuneAdvisor::Profile::Efficient, TuneAdvisor::Profile::Balanced,
TuneAdvisor::Profile::Aggressive}) {
ProfileGuard profileGuard(profile);
uint32_t budget = TuneAdvisor::postIngestTotalConcurrent();
if (budget < 6)
continue; // Only test when budget can cover all stages
uint32_t ext = TuneAdvisor::postExtractionConcurrent();
uint32_t kg = TuneAdvisor::postKgConcurrent();
uint32_t sym = TuneAdvisor::postSymbolConcurrent();
uint32_t ent = TuneAdvisor::postEntityConcurrent();
uint32_t tit = TuneAdvisor::postTitleConcurrent();
uint32_t emb = TuneAdvisor::postEmbedConcurrent();
INFO("hw=" << hw << " profile=" << static_cast<int>(profile)
<< " budget=" << budget);
INFO("ext=" << ext << " kg=" << kg << " sym=" << sym << " ent=" << ent
<< " tit=" << tit << " emb=" << emb);
CHECK(ext >= 1);
CHECK(kg >= 1);
CHECK(sym >= 1);
CHECK(ent >= 1);
CHECK(tit >= 1);
CHECK(emb >= 1);
}
}
}
SECTION("Sum of per-stage allocations does not exceed budget") {
resetPostIngestOverrides();
setAllPostIngestStagesActive();
ScopedEnvVar envPostIngest("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
ScopedEnvVar envMaxThreads("YAMS_MAX_THREADS", "0");
for (unsigned hw : {2u, 4u, 8u, 16u, 32u}) {
HwGuard hwGuard(hw);
for (auto profile : {TuneAdvisor::Profile::Efficient, TuneAdvisor::Profile::Balanced,
TuneAdvisor::Profile::Aggressive}) {
ProfileGuard profileGuard(profile);
uint32_t budget = TuneAdvisor::postIngestTotalConcurrent();
uint32_t sum = sumAllStages();
INFO("hw=" << hw << " profile=" << static_cast<int>(profile) << " budget=" << budget
<< " sum=" << sum);
CHECK(sum <= budget);
}
}
}
SECTION("Extraction and embed get minimum 1 when budget >= 2") {
resetPostIngestOverrides();
setAllPostIngestStagesActive();
ScopedEnvVar envPostIngest("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
ScopedEnvVar envMaxThreads("YAMS_MAX_THREADS", "0");
for (unsigned hw : {2u, 3u, 4u}) {
HwGuard hwGuard(hw);
for (auto profile : {TuneAdvisor::Profile::Efficient, TuneAdvisor::Profile::Balanced,
TuneAdvisor::Profile::Aggressive}) {
ProfileGuard profileGuard(profile);
uint32_t budget = TuneAdvisor::postIngestTotalConcurrent();
uint32_t ext = TuneAdvisor::postExtractionConcurrent();
uint32_t emb = TuneAdvisor::postEmbedConcurrent();
INFO("hw=" << hw << " profile=" << static_cast<int>(profile)
<< " budget=" << budget);
if (budget >= 2) {
CHECK(ext >= 1);
CHECK(emb >= 1);
}
}
}
}
SECTION("Embed gets higher weight than individual other stages") {
// Embed has weight 2 vs weight 1 for others; with sufficient budget,
// embed should get at least as much as any single other stage.
resetPostIngestOverrides();
setAllPostIngestStagesActive();
ScopedEnvVar envPostIngest("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
ScopedEnvVar envMaxThreads("YAMS_MAX_THREADS", "0");
for (unsigned hw : {16u, 24u, 32u}) {
HwGuard hwGuard(hw);
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
uint32_t emb = TuneAdvisor::postEmbedConcurrent();
uint32_t kg = TuneAdvisor::postKgConcurrent();
uint32_t sym = TuneAdvisor::postSymbolConcurrent();
INFO("hw=" << hw << " emb=" << emb << " kg=" << kg << " sym=" << sym);
CHECK(emb >= kg);
CHECK(emb >= sym);
}
}
}
TEST_CASE("Contention-aware budget adjustment logic",
"[daemon][tune][reconciliation][contention][catch2]") {
SECTION("Severe contention drops budget aggressively") {
const int32_t adjust = TuningManager::testing_computeContentionBudgetAdjustment(
/*waitingRequests=*/3,
/*waitMicrosDelta=*/25000,
/*timeoutDelta=*/1,
/*failedDelta=*/0,
/*processedDelta=*/6,
/*healthyTicks=*/0);
CHECK(adjust == -2);
}
SECTION("Moderate contention applies gentle reduction") {
const int32_t adjust = TuningManager::testing_computeContentionBudgetAdjustment(
/*waitingRequests=*/2,
/*waitMicrosDelta=*/9000,
/*timeoutDelta=*/0,
/*failedDelta=*/0,
/*processedDelta=*/6,
/*healthyTicks=*/0);
CHECK(adjust == -1);
}
SECTION("Healthy sustained throughput allows gradual increase") {
const int32_t adjust = TuningManager::testing_computeContentionBudgetAdjustment(
/*waitingRequests=*/0,
/*waitMicrosDelta=*/500,
/*timeoutDelta=*/0,
/*failedDelta=*/0,
/*processedDelta=*/6,
/*healthyTicks=*/2);
CHECK(adjust == 1);
}
SECTION("Insufficient healthy history keeps budget steady") {
const int32_t adjust = TuningManager::testing_computeContentionBudgetAdjustment(
/*waitingRequests=*/0,
/*waitMicrosDelta=*/500,
/*timeoutDelta=*/0,
/*failedDelta=*/0,
/*processedDelta=*/6,
/*healthyTicks=*/1);
CHECK(adjust == 0);
}
}
// =============================================================================
// Group A2: Active mask filtering
// =============================================================================
TEST_CASE("Post-ingest active mask filters stages correctly",
"[daemon][tune][reconciliation][catch2]") {
SECTION("Disabled stages get 0 allocation") {
resetPostIngestOverrides();
ScopedEnvVar envPostIngest("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
ScopedEnvVar envMaxThreads("YAMS_MAX_THREADS", "0");
HwGuard hwGuard(16);
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
// Enable only extraction and embed (disable kg, symbol, entity, title)
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Extraction, true);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::KnowledgeGraph, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Symbol, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Entity, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Title, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Embed, true);
uint32_t kg = TuneAdvisor::postKgConcurrent();
uint32_t sym = TuneAdvisor::postSymbolConcurrent();
uint32_t ent = TuneAdvisor::postEntityConcurrent();
uint32_t tit = TuneAdvisor::postTitleConcurrent();
uint32_t ext = TuneAdvisor::postExtractionConcurrent();
uint32_t emb = TuneAdvisor::postEmbedConcurrent();
CHECK(kg == 0);
CHECK(sym == 0);
CHECK(ent == 0);
CHECK(tit == 0);
CHECK(ext >= 1);
CHECK(emb >= 1);
// Budget redistributed to enabled stages — they should get more
// than when all 6 are active
setAllPostIngestStagesActive();
uint32_t extAll = TuneAdvisor::postExtractionConcurrent();
uint32_t embAll = TuneAdvisor::postEmbedConcurrent();
INFO("ext(2 active)=" << ext << " ext(all active)=" << extAll);
INFO("emb(2 active)=" << emb << " emb(all active)=" << embAll);
CHECK(ext >= extAll);
CHECK(emb >= embAll);
}
SECTION("Disabling all stages reduces total budget") {
resetPostIngestOverrides();
HwGuard hwGuard(8);
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
uint32_t budgetAllActive = TuneAdvisor::postIngestTotalConcurrent();
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Extraction, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::KnowledgeGraph, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Symbol, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Entity, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Title, false);
TuneAdvisor::setPostIngestStageActive(TuneAdvisor::PostIngestStage::Embed, false);
// When all disabled, the total budget floor clamps at 0 active stages
// but per-stage getters still return their computed values (the active
// mask only affects the total budget, not individual allocations).
uint32_t budgetNoneActive = TuneAdvisor::postIngestTotalConcurrent();
INFO("budget(all active)=" << budgetAllActive
<< " budget(none active)=" << budgetNoneActive);
CHECK(budgetNoneActive <= budgetAllActive);
// Restore for subsequent tests
setAllPostIngestStagesActive();
}
}
// =============================================================================
// Group A3: Per-stage override clamping
// =============================================================================
TEST_CASE("Per-stage overrides are respected and clamped",
"[daemon][tune][reconciliation][catch2]") {
SECTION("Override takes precedence over computed allocation") {
resetPostIngestOverrides();
setAllPostIngestStagesActive();
HwGuard hwGuard(16);
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
// Set a specific override for extraction
TuneAdvisor::setPostExtractionConcurrent(5);
uint32_t ext = TuneAdvisor::postExtractionConcurrent();
CHECK(ext == 5);
// Reset
TuneAdvisor::setPostExtractionConcurrent(0);
}
SECTION("Override is clamped to max cap") {
resetPostIngestOverrides();
setAllPostIngestStagesActive();
HwGuard hwGuard(16);
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
// Entity max cap is 16 — setting beyond should be clamped
TuneAdvisor::setPostEntityConcurrent(16);
uint32_t ent = TuneAdvisor::postEntityConcurrent();
INFO("entity with override=16: " << ent);
CHECK(ent <= 16);
// Reset
TuneAdvisor::setPostEntityConcurrent(0);
}
SECTION("Overrides are independent — sum may exceed budget (no runtime clamping)") {
resetPostIngestOverrides();
setAllPostIngestStagesActive();
HwGuard hwGuard(8);
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
// TuneAdvisor per-stage overrides are not clamped to the total budget.
// The TuningManager reconciliation loop is responsible for capping at runtime.
// Verify overrides take effect individually.
TuneAdvisor::setPostExtractionConcurrent(3);
TuneAdvisor::setPostKgConcurrent(3);
TuneAdvisor::setPostEmbedConcurrent(3);
CHECK(TuneAdvisor::postExtractionConcurrent() == 3);
CHECK(TuneAdvisor::postKgConcurrent() == 3);
CHECK(TuneAdvisor::postEmbedConcurrent() == 3);
uint32_t budget = TuneAdvisor::postIngestTotalConcurrent();
uint32_t sum = sumAllStages();
INFO("budget=" << budget << " sum=" << sum);
// Sum CAN exceed budget — that's by design; TuningManager reconciles at runtime
CHECK(sum >= 9); // at least the 3 overridden stages
resetPostIngestOverrides();
}
}
// =============================================================================
// Group B: Pool configuration minimum guards
// =============================================================================
TEST_CASE("Pool configuration defaults are reasonable",
"[daemon][tune][reconciliation][pool][catch2]") {
SECTION("poolMinSizeIpc defaults to at least 1") {
uint32_t minIpc = TuneAdvisor::poolMinSizeIpc();
INFO("poolMinSizeIpc default=" << minIpc);
CHECK(minIpc >= 1);
}
SECTION("poolMinSizeIpcIo defaults to at least 1") {
uint32_t minIo = TuneAdvisor::poolMinSizeIpcIo();
INFO("poolMinSizeIpcIo default=" << minIo);
CHECK(minIo >= 1);
}
SECTION("poolMaxSizeIpc is greater than poolMinSizeIpc") {
uint32_t minIpc = TuneAdvisor::poolMinSizeIpc();
uint32_t maxIpc = TuneAdvisor::poolMaxSizeIpc();
INFO("IPC pool: min=" << minIpc << " max=" << maxIpc);
CHECK(maxIpc > minIpc);
}
SECTION("poolMaxSizeIpcIo is greater than poolMinSizeIpcIo") {
uint32_t minIo = TuneAdvisor::poolMinSizeIpcIo();
uint32_t maxIo = TuneAdvisor::poolMaxSizeIpcIo();
INFO("IO pool: min=" << minIo << " max=" << maxIo);
CHECK(maxIo > minIo);
}
SECTION("IO pool min_size should be at least 2 after guard") {
// This tests the ServiceManager guard: IO pool should have min >= 2.
// The raw TuneAdvisor default is 1, but ServiceManager should guard it.
// We test the expected post-guard value here.
uint32_t rawMin = TuneAdvisor::poolMinSizeIpcIo();
uint32_t guardedMin = rawMin < 2 ? 2 : rawMin;
INFO("raw min=" << rawMin << " guarded min=" << guardedMin);
CHECK(guardedMin >= 2);
}
SECTION("recommendedThreads never returns 0") {
for (unsigned hw : {1u, 2u, 4u}) {
HwGuard hwGuard(hw);
for (auto profile : {TuneAdvisor::Profile::Efficient, TuneAdvisor::Profile::Balanced,
TuneAdvisor::Profile::Aggressive}) {
ProfileGuard profileGuard(profile);
uint32_t threads = TuneAdvisor::recommendedThreads(0.5);
INFO("hw=" << hw << " profile=" << static_cast<int>(profile)
<< " recommendedThreads(0.5)=" << threads);
CHECK(threads >= 1);
}
}
}
}
// =============================================================================
// Group C: ETA remaining calculation accuracy
// =============================================================================
TEST_CASE("ETA remaining calculation handles integer truncation",
"[daemon][tune][reconciliation][eta][catch2]") {
// Test the ETA formula: remain = max(0, exp - (exp * progress + 99) / 100)
// The old formula was: remain = max(0, exp - (exp * progress) / 100)
// which suffers from integer truncation for small exp values.
auto computeEtaRemaining = [](int exp, int progress) -> int {
return ServiceManager::computeEtaRemaining(exp, progress);
};
SECTION("Zero progress gives full ETA") {
CHECK(computeEtaRemaining(5, 0) == 5);
CHECK(computeEtaRemaining(1, 0) == 1);
CHECK(computeEtaRemaining(20, 0) == 20);
}
SECTION("100% progress gives 0 ETA") {
CHECK(computeEtaRemaining(5, 100) == 0);
CHECK(computeEtaRemaining(1, 100) == 0);
CHECK(computeEtaRemaining(20, 100) == 0);
}
SECTION("50% progress on small exp should reduce ETA") {
// With exp=1, progress=50: old formula gives (1*50)/100=0, remain=1
// Fixed formula should give remain <= 1 (ideally 0, since half is done)
int remain = computeEtaRemaining(1, 50);
INFO("exp=1, progress=50, remain=" << remain);
CHECK(remain <= 1);
CHECK(remain >= 0);
}
SECTION("75% progress on small exp should reduce ETA significantly") {
// With exp=1, progress=75: old formula gives (1*75)/100=0, remain=1
// Fixed formula should give 0
int remain = computeEtaRemaining(1, 75);
INFO("exp=1, progress=75, remain=" << remain);
CHECK(remain == 0);
}
SECTION("ETA is monotonically non-increasing as progress increases") {
for (int exp : {1, 2, 3, 5, 10, 20}) {
int prev = exp;
for (int progress = 0; progress <= 100; progress += 10) {
int remain = computeEtaRemaining(exp, progress);
INFO("exp=" << exp << " progress=" << progress << " remain=" << remain
<< " prev=" << prev);
CHECK(remain <= prev);
CHECK(remain >= 0);
prev = remain;
}
}
}
SECTION("Normal sized exp values are accurate") {
// exp=5, progress=50: should be about 2-3
int r1 = computeEtaRemaining(5, 50);
CHECK(r1 >= 2);
CHECK(r1 <= 3);
// exp=10, progress=30: should be about 7
int r2 = computeEtaRemaining(10, 30);
CHECK(r2 >= 6);
CHECK(r2 <= 7);
// exp=20, progress=90: should be about 2
int r3 = computeEtaRemaining(20, 90);
CHECK(r3 >= 1);
CHECK(r3 <= 2);
}
SECTION("Negative and edge cases are safe") {
CHECK(computeEtaRemaining(0, 0) == 0);
CHECK(computeEtaRemaining(0, 50) == 0);
CHECK(computeEtaRemaining(0, 100) == 0);
// Progress beyond 100 should still be safe
CHECK(computeEtaRemaining(5, 150) == 0);
}
}
// =============================================================================
// Group D: Embed scale bias under backlog pressure
// =============================================================================
TEST_CASE("Embed scale bias ramps with queue pressure",
"[daemon][tune][reconciliation][embed-bias][catch2]") {
SECTION("Bias increases as embed queue grows") {
const auto b0 = TuningManager::testing_computeEmbedScaleBias(0, 0, 0, 0);
const auto b1 = TuningManager::testing_computeEmbedScaleBias(200, 0, 0, 0);
const auto b2 = TuningManager::testing_computeEmbedScaleBias(800, 0, 0, 0);
const auto b3 = TuningManager::testing_computeEmbedScaleBias(2200, 0, 0, 0);
CHECK(b1 >= b0);
CHECK(b2 >= b1);
CHECK(b3 >= b2);
}
SECTION("Drops and saturation increase bias beyond pure queue level") {
const auto queueOnly = TuningManager::testing_computeEmbedScaleBias(900, 0, 0, 0);
const auto withDrops = TuningManager::testing_computeEmbedScaleBias(900, 50, 0, 0);
const auto withSaturation = TuningManager::testing_computeEmbedScaleBias(900, 50, 900, 1);
CHECK(withDrops >= queueOnly);
CHECK(withSaturation >= withDrops);
}
SECTION("Bias is bounded to avoid runaway oversubscription") {
const auto capped = TuningManager::testing_computeEmbedScaleBias(100000, 100000, 100000, 8);
CHECK(capped <= 8);
}
}
TEST_CASE("Queue-pressure rebalance shifts capacity to backlogged stages",
"[daemon][tune][reconciliation][queue][catch2]") {
SECTION("Shifts one slot from low-pressure to high-pressure stage") {
std::array<uint32_t, 6> targets{2, 2, 2, 2, 2, 2};
std::array<uint32_t, 6> floors{1, 0, 0, 0, 0, 0};
std::array<std::size_t, 6> depths{0, 400, 1, 1, 1, 1};
std::array<bool, 6> active{true, true, true, true, true, true};
TuningManager::testing_rebalanceTargetsByQueue(targets, floors, depths, active);
// KG backlog dominates, so KG should gain capacity.
CHECK(targets[1] > 2);
// Total budget must be preserved.
CHECK(targets[0] + targets[1] + targets[2] + targets[3] + targets[4] + targets[5] == 12);
}
SECTION("Does not violate configured floors") {
std::array<uint32_t, 6> targets{1, 3, 1, 1, 1, 1};
std::array<uint32_t, 6> floors{1, 0, 0, 0, 0, 0};
std::array<std::size_t, 6> depths{0, 0, 0, 0, 0, 500};
std::array<bool, 6> active{true, true, true, true, true, true};
TuningManager::testing_rebalanceTargetsByQueue(targets, floors, depths, active);
CHECK(targets[0] >= floors[0]);
CHECK(targets[5] > 1);
CHECK(targets[0] + targets[1] + targets[2] + targets[3] + targets[4] + targets[5] == 8);
}
}
TEST_CASE("Post-ingest scale test mode isolates zero-target policy",
"[daemon][tune][reconciliation][scale-mode][catch2]") {
class ScaleModeGuard {
public:
ScaleModeGuard() : previous_(TuningManager::testing_postIngestScaleTestMode()) {}
~ScaleModeGuard() { TuningManager::testing_setPostIngestScaleTestMode(previous_); }
private:
TuningManager::PostIngestScaleTestMode previous_;
} guard;
SECTION("Normal mode allows zero only when daemon is idle and not busy") {
TuningManager::testing_setPostIngestScaleTestMode(
TuningManager::PostIngestScaleTestMode::Normal);
CHECK(TuningManager::testing_shouldAllowZeroPostIngestTargets(true, false));
CHECK_FALSE(TuningManager::testing_shouldAllowZeroPostIngestTargets(false, false));
CHECK_FALSE(TuningManager::testing_shouldAllowZeroPostIngestTargets(true, true));
CHECK_FALSE(TuningManager::testing_shouldAllowZeroPostIngestTargets(false, true));
}
SECTION("ForceBusy mode disables zero-target scale-down") {
TuningManager::testing_setPostIngestScaleTestMode(
TuningManager::PostIngestScaleTestMode::ForceBusy);
CHECK_FALSE(TuningManager::testing_shouldAllowZeroPostIngestTargets(true, false));
CHECK_FALSE(TuningManager::testing_shouldAllowZeroPostIngestTargets(false, false));
CHECK_FALSE(TuningManager::testing_shouldAllowZeroPostIngestTargets(true, true));
}
SECTION("ForceIdle mode enables zero-target scale-down") {
TuningManager::testing_setPostIngestScaleTestMode(
TuningManager::PostIngestScaleTestMode::ForceIdle);
CHECK(TuningManager::testing_shouldAllowZeroPostIngestTargets(true, false));
CHECK(TuningManager::testing_shouldAllowZeroPostIngestTargets(false, false));
CHECK(TuningManager::testing_shouldAllowZeroPostIngestTargets(true, true));
CHECK(TuningManager::testing_shouldAllowZeroPostIngestTargets(false, true));
}
}
// =============================================================================
// Group E: Deterministic tuning simulation with GradientLimiter
// =============================================================================
TEST_CASE("Embed tuning simulation improves queue control with gradient limiter",
"[daemon][tune][reconciliation][gradient][sim][!benchmark]") {
struct SimResult {
std::size_t peakQueue{0};
std::size_t finalQueue{0};
std::size_t totalProcessed{0};
double avgTarget{0.0};
};
auto runSim = [&](bool useDynamicBias) -> SimResult {
GradientLimiter::Config cfg;
cfg.initialLimit = 4.0;
cfg.minLimit = 1.0;
cfg.maxLimit = 32.0;
cfg.warmupSamples = 3;
cfg.smoothingAlpha = 0.5;
cfg.longWindowAlpha = 0.1;
cfg.tolerance = 1.5;
GradientLimiter limiter("embed-sim", cfg);
constexpr std::size_t kTicks = 180;
constexpr uint32_t kBaseTarget = 2;
constexpr std::size_t kPerWorkerRate = 22;
constexpr std::size_t kDropThreshold = 1100;
std::size_t queue = 0;
std::size_t peakQueue = 0;
std::size_t totalProcessed = 0;
std::size_t targetAccumulator = 0;
for (std::size_t tick = 0; tick < kTicks; ++tick) {
std::size_t arrivals = 0;
if (tick < 40) {
arrivals = 35; // warm ramp
} else if (tick < 110) {
arrivals = 70; // sustained burst
} else {
arrivals = 18; // recovery tail
}
const uint64_t droppedDelta =
queue > kDropThreshold ? static_cast<uint64_t>((queue - kDropThreshold) / 60) : 0u;
const std::size_t inFlightHint = queue > 0 ? 1u : 0u;
const uint32_t bias = useDynamicBias ? TuningManager::testing_computeEmbedScaleBias(
queue, droppedDelta, queue, inFlightHint)
: 0u;
const uint32_t target = kBaseTarget + bias;
targetAccumulator += target;
const uint32_t limiterSlots = std::max<uint32_t>(1u, limiter.effectiveLimit());
const uint32_t workers = std::min(target, limiterSlots);
const std::size_t demand = queue + arrivals;
const std::size_t capacity = static_cast<std::size_t>(workers) * kPerWorkerRate;
const std::size_t processed = std::min(demand, capacity);
totalProcessed += processed;
queue = demand - processed;
peakQueue = std::max(peakQueue, queue);
// Drive gradient limiter using queue-correlated RTTs.
const uint32_t simulatedRttMs =
static_cast<uint32_t>(8 + std::min<std::size_t>(120, queue / 35));
const uint32_t inflightForLimiter = std::max<uint32_t>(1u, workers);
uint32_t acquired = 0;
for (uint32_t i = 0; i < inflightForLimiter; ++i) {
if (limiter.tryAcquire()) {
++acquired;
}
}
for (uint32_t i = 0; i < acquired; ++i) {
limiter.onJobComplete(std::chrono::milliseconds(simulatedRttMs), true);
}
}
SimResult out;
out.peakQueue = peakQueue;
out.finalQueue = queue;
out.totalProcessed = totalProcessed;
out.avgTarget = static_cast<double>(targetAccumulator) / static_cast<double>(kTicks);
return out;
};
const SimResult baseline = runSim(false);
const SimResult dynamic = runSim(true);
INFO("baseline peak=" << baseline.peakQueue << " final=" << baseline.finalQueue << " processed="
<< baseline.totalProcessed << " avgTarget=" << baseline.avgTarget);
INFO("dynamic peak=" << dynamic.peakQueue << " final=" << dynamic.finalQueue << " processed="
<< dynamic.totalProcessed << " avgTarget=" << dynamic.avgTarget);
// Dynamic bias should improve drain behavior under the same gradient limiter.
CHECK(dynamic.totalProcessed >= baseline.totalProcessed);
CHECK(dynamic.peakQueue <= baseline.peakQueue);
CHECK(dynamic.finalQueue <= baseline.finalQueue);
CHECK(dynamic.avgTarget >= baseline.avgTarget);
}
// =============================================================================
// Group F: End-to-end idle zero-targets via DynamicCap (Bug 1 + Bug 4 combined)
// =============================================================================
// When the tuning loop writes DynamicCap(0) to all stages (idle path), the
// effective per-stage concurrency must be exactly zero. This tests the full
// chain: TuningManager writes caps → TuneAdvisor allocator respects them.
//
// TDD Red Phase: FAILS because DynamicCap(0) is the unset sentinel (Bug 1)
// AND the allocator has hardcoded minimums (Bug 4).
TEST_CASE("Idle DynamicCap(0) on all stages yields zero effective concurrency (E2E)",
"[daemon][tune][reconciliation][idle][e2e][catch2]") {
resetPostIngestOverrides();
setAllPostIngestStagesActive();
HwGuard hwGuard(16);
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
TuneAdvisor::setPostIngestTotalConcurrent(12);
// Simulate what TuningManager::tick_once does when idle: write DynamicCap(0) to all stages
TuneAdvisor::beginDynamicCapWrite();
TuneAdvisor::setPostExtractionConcurrentDynamicCap(0);
TuneAdvisor::setPostKgConcurrentDynamicCap(0);
TuneAdvisor::setPostSymbolConcurrentDynamicCap(0);
TuneAdvisor::setPostEntityConcurrentDynamicCap(0);
TuneAdvisor::setPostTitleConcurrentDynamicCap(0);
TuneAdvisor::setPostEmbedConcurrentDynamicCap(0);
TuneAdvisor::endDynamicCapWrite();
// Read back via the public per-stage accessors (includes DynamicCaps)
const uint32_t total = TuneAdvisor::postExtractionConcurrent() +
TuneAdvisor::postKgConcurrent() + TuneAdvisor::postSymbolConcurrent() +
TuneAdvisor::postEntityConcurrent() +
TuneAdvisor::postTitleConcurrent() + TuneAdvisor::postEmbedConcurrent();
// BUG: DynamicCap(0) = unset sentinel → caps ignored → total >= 6
// FIXED: total == 0
CHECK(total == 0);
}
TEST_CASE("Clearing DynamicCaps after idle restores non-zero defaults (E2E)",
"[daemon][tune][reconciliation][idle][e2e][recovery][catch2]") {
resetPostIngestOverrides();
setAllPostIngestStagesActive();
HwGuard hwGuard(16);
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
TuneAdvisor::setPostIngestTotalConcurrent(12);
// Capture defaults
const auto defaultExtract = TuneAdvisor::postExtractionDefaultConcurrent();
const auto defaultEmbed = TuneAdvisor::postEmbedDefaultConcurrent();
REQUIRE(defaultExtract >= 1);
REQUIRE(defaultEmbed >= 1);
// Go idle: cap everything to 0
TuneAdvisor::beginDynamicCapWrite();
TuneAdvisor::setPostExtractionConcurrentDynamicCap(0);
TuneAdvisor::setPostKgConcurrentDynamicCap(0);
TuneAdvisor::setPostSymbolConcurrentDynamicCap(0);
TuneAdvisor::setPostEntityConcurrentDynamicCap(0);
TuneAdvisor::setPostTitleConcurrentDynamicCap(0);
TuneAdvisor::setPostEmbedConcurrentDynamicCap(0);
TuneAdvisor::endDynamicCapWrite();
// Verify idle state (after Bug 1+4 fix)
CHECK(TuneAdvisor::postExtractionConcurrent() == 0);
CHECK(TuneAdvisor::postEmbedConcurrent() == 0);
// Wake up: clear caps (set to UINT32_MAX sentinel — after Bug 1 fix)
// In current code, clearing is done by writing 0. After fix, clearing
// is done by writing UINT32_MAX.
TuneAdvisor::beginDynamicCapWrite();
TuneAdvisor::setPostExtractionConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostKgConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostSymbolConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostEntityConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostTitleConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::setPostEmbedConcurrentDynamicCap(UINT32_MAX);
TuneAdvisor::endDynamicCapWrite();
// Defaults should be fully restored
CHECK(TuneAdvisor::postExtractionConcurrent() == defaultExtract);
CHECK(TuneAdvisor::postEmbedConcurrent() == defaultEmbed);
}