* 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
1004 lines
39 KiB
C++
1004 lines
39 KiB
C++
// TuneAdvisor model eviction threshold tests (Catch2)
|
|
// Tests profile defaults, env var overrides, programmatic setters, and validation
|
|
|
|
#include <catch2/catch_approx.hpp>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <yams/daemon/components/TuneAdvisor.h>
|
|
#include <yams/daemon/resource/gpu_info.h>
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <string>
|
|
|
|
#include "../../common/env_compat.h"
|
|
#include "../../common/test_helpers_catch2.h"
|
|
#include <yams/compat/unistd.h>
|
|
|
|
using namespace yams::daemon;
|
|
|
|
// =============================================================================
|
|
// Test Helpers
|
|
// =============================================================================
|
|
|
|
namespace {
|
|
|
|
/// RAII guard for TuneAdvisor profile restoration
|
|
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 HardwareGuard {
|
|
unsigned prev_;
|
|
|
|
public:
|
|
explicit HardwareGuard(unsigned hw) : prev_(TuneAdvisor::hardwareConcurrency()) {
|
|
TuneAdvisor::setHardwareConcurrencyForTests(hw);
|
|
}
|
|
~HardwareGuard() { TuneAdvisor::setHardwareConcurrencyForTests(prev_); }
|
|
|
|
HardwareGuard(const HardwareGuard&) = delete;
|
|
HardwareGuard& operator=(const HardwareGuard&) = delete;
|
|
};
|
|
|
|
/// Reset all eviction threshold overrides to default (unset)
|
|
void resetEvictionOverrides() {
|
|
TuneAdvisor::resetModelEvictThresholdOverrides();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// =============================================================================
|
|
// Profile Default Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("Model eviction threshold profile defaults", "[daemon][tune][advisor][catch2]") {
|
|
resetEvictionOverrides();
|
|
|
|
SECTION("Efficient profile thresholds") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.30));
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(0.50));
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(0.70));
|
|
}
|
|
|
|
SECTION("Balanced profile thresholds") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.60));
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(0.75));
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(0.90));
|
|
}
|
|
|
|
SECTION("Aggressive profile thresholds") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.75));
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(0.85));
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(0.95));
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Environment Variable Override Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("Model eviction threshold env var overrides", "[daemon][tune][advisor][catch2]") {
|
|
resetEvictionOverrides();
|
|
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
|
|
|
|
SECTION("Valid warning threshold env var overrides profile default") {
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_WARNING_THRESHOLD", "0.45");
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.45));
|
|
}
|
|
|
|
SECTION("Valid critical threshold env var overrides profile default") {
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_CRITICAL_THRESHOLD", "0.80");
|
|
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(0.80));
|
|
}
|
|
|
|
SECTION("Valid emergency threshold env var overrides profile default") {
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_EMERGENCY_THRESHOLD", "0.92");
|
|
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(0.92));
|
|
}
|
|
|
|
SECTION("Invalid env var (non-numeric) falls back to profile default") {
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_WARNING_THRESHOLD", "invalid");
|
|
|
|
// Should return profile default (0.60 for Balanced), not crash
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.60));
|
|
}
|
|
|
|
SECTION("Env var exactly 1.0 is rejected (must be < 1.0)") {
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_WARNING_THRESHOLD", "1.0");
|
|
|
|
// 1.0 should be rejected, falls back to profile default
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.60));
|
|
}
|
|
|
|
SECTION("Env var exactly 0.0 is rejected (must be > 0.0)") {
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_WARNING_THRESHOLD", "0.0");
|
|
|
|
// 0.0 should be rejected, falls back to profile default
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.60));
|
|
}
|
|
|
|
SECTION("Negative env var is rejected") {
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_WARNING_THRESHOLD", "-0.5");
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.60));
|
|
}
|
|
|
|
SECTION("Env var > 1.0 is rejected") {
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_WARNING_THRESHOLD", "1.5");
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.60));
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Programmatic Setter Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("Model eviction threshold setter validation", "[daemon][tune][advisor][catch2]") {
|
|
resetEvictionOverrides();
|
|
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
|
|
|
|
SECTION("Valid values are accepted") {
|
|
TuneAdvisor::setModelEvictWarningThreshold(0.5);
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.5));
|
|
|
|
TuneAdvisor::setModelEvictCriticalThreshold(0.7);
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(0.7));
|
|
|
|
TuneAdvisor::setModelEvictEmergencyThreshold(0.85);
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(0.85));
|
|
}
|
|
|
|
SECTION("Boundary values near 0 and 1 are handled correctly") {
|
|
// Just above 0 should be accepted
|
|
TuneAdvisor::setModelEvictWarningThreshold(0.001);
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.001));
|
|
|
|
// Just below 1 should be accepted
|
|
TuneAdvisor::setModelEvictCriticalThreshold(0.999);
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(0.999));
|
|
}
|
|
|
|
SECTION("Negative values are rejected") {
|
|
double prev = TuneAdvisor::modelEvictWarningThreshold();
|
|
TuneAdvisor::setModelEvictWarningThreshold(-0.5);
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(prev));
|
|
}
|
|
|
|
SECTION("Zero is rejected") {
|
|
double prev = TuneAdvisor::modelEvictCriticalThreshold();
|
|
TuneAdvisor::setModelEvictCriticalThreshold(0.0);
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(prev));
|
|
}
|
|
|
|
SECTION("Values >= 1.0 are rejected") {
|
|
double prev = TuneAdvisor::modelEvictEmergencyThreshold();
|
|
|
|
TuneAdvisor::setModelEvictEmergencyThreshold(1.0);
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(prev));
|
|
|
|
TuneAdvisor::setModelEvictEmergencyThreshold(1.5);
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(prev));
|
|
|
|
TuneAdvisor::setModelEvictEmergencyThreshold(100.0);
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(prev));
|
|
}
|
|
|
|
SECTION("NaN is rejected") {
|
|
double prev = TuneAdvisor::modelEvictWarningThreshold();
|
|
TuneAdvisor::setModelEvictWarningThreshold(std::numeric_limits<double>::quiet_NaN());
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(prev));
|
|
}
|
|
|
|
SECTION("Positive infinity is rejected") {
|
|
double prev = TuneAdvisor::modelEvictCriticalThreshold();
|
|
TuneAdvisor::setModelEvictCriticalThreshold(std::numeric_limits<double>::infinity());
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(prev));
|
|
}
|
|
|
|
SECTION("Negative infinity is rejected") {
|
|
double prev = TuneAdvisor::modelEvictEmergencyThreshold();
|
|
TuneAdvisor::setModelEvictEmergencyThreshold(-std::numeric_limits<double>::infinity());
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(prev));
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Override Precedence Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("Model eviction threshold override precedence", "[daemon][tune][advisor][catch2]") {
|
|
resetEvictionOverrides();
|
|
|
|
SECTION("Programmatic setter takes precedence over env var") {
|
|
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_WARNING_THRESHOLD", "0.45");
|
|
|
|
// Env var would give 0.45, but setter should override
|
|
TuneAdvisor::setModelEvictWarningThreshold(0.55);
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(0.55));
|
|
}
|
|
|
|
SECTION("Env var takes precedence over profile default") {
|
|
ProfileGuard profileGuard(TuneAdvisor::Profile::Efficient);
|
|
ScopedEnvVar envGuard("YAMS_MODEL_EVICT_CRITICAL_THRESHOLD", "0.65");
|
|
|
|
// Efficient profile default is 0.50, but env var overrides to 0.65
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(0.65));
|
|
}
|
|
|
|
SECTION("Profile default is used when no overrides set") {
|
|
ProfileGuard profileGuard(TuneAdvisor::Profile::Aggressive);
|
|
|
|
CHECK(TuneAdvisor::modelEvictEmergencyThreshold() == Catch::Approx(0.95));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Governor warning scale percent tuning", "[daemon][tune][advisor][catch2]") {
|
|
TuneAdvisor::resetGovernorWarningScalePercentOverride();
|
|
|
|
SECTION("Default value is 85 percent") {
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 85u);
|
|
}
|
|
|
|
SECTION("Valid env var overrides default") {
|
|
ScopedEnvVar envGuard("YAMS_GOV_WARNING_SCALE_PCT", "92");
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 92u);
|
|
}
|
|
|
|
SECTION("Invalid env var falls back to default") {
|
|
ScopedEnvVar envGuard("YAMS_GOV_WARNING_SCALE_PCT", "invalid");
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 85u);
|
|
}
|
|
|
|
SECTION("Out-of-range env var falls back to default") {
|
|
ScopedEnvVar envGuard("YAMS_GOV_WARNING_SCALE_PCT", "9");
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 85u);
|
|
}
|
|
|
|
SECTION("Programmatic setter overrides env var") {
|
|
ScopedEnvVar envGuard("YAMS_GOV_WARNING_SCALE_PCT", "90");
|
|
TuneAdvisor::setGovernorWarningScalePercent(77u);
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 77u);
|
|
}
|
|
|
|
SECTION("Setter clamps values into allowed range") {
|
|
TuneAdvisor::setGovernorWarningScalePercent(5u);
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 10u);
|
|
|
|
TuneAdvisor::setGovernorWarningScalePercent(150u);
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 100u);
|
|
}
|
|
|
|
SECTION("Setter zero resets override") {
|
|
TuneAdvisor::setGovernorWarningScalePercent(72u);
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 72u);
|
|
|
|
TuneAdvisor::setGovernorWarningScalePercent(0u);
|
|
CHECK(TuneAdvisor::governorWarningScalePercent() == 85u);
|
|
}
|
|
|
|
TuneAdvisor::resetGovernorWarningScalePercentOverride();
|
|
}
|
|
|
|
TEST_CASE("Connection lifetime seconds env var", "[daemon][tune][advisor][catch2]") {
|
|
TuneAdvisor::resetConnectionLifetimeSecondsOverride();
|
|
|
|
SECTION("Default value is 300 seconds") {
|
|
CHECK(TuneAdvisor::connectionLifetimeSeconds() == 300);
|
|
}
|
|
|
|
SECTION("Valid env var overrides default") {
|
|
ScopedEnvVar envGuard("YAMS_CONNECTION_LIFETIME_S", "1800");
|
|
CHECK(TuneAdvisor::connectionLifetimeSeconds() == 1800);
|
|
}
|
|
|
|
SECTION("Zero env var disables lifetime enforcement") {
|
|
ScopedEnvVar envGuard("YAMS_CONNECTION_LIFETIME_S", "0");
|
|
CHECK(TuneAdvisor::connectionLifetimeSeconds() == 0);
|
|
}
|
|
|
|
SECTION("Out-of-range env var falls back to default") {
|
|
ScopedEnvVar envGuard("YAMS_CONNECTION_LIFETIME_S", "90000");
|
|
CHECK(TuneAdvisor::connectionLifetimeSeconds() == 300);
|
|
}
|
|
|
|
SECTION("Programmatic setter overrides env") {
|
|
ScopedEnvVar envGuard("YAMS_CONNECTION_LIFETIME_S", "1800");
|
|
TuneAdvisor::setConnectionLifetimeSeconds(42);
|
|
CHECK(TuneAdvisor::connectionLifetimeSeconds() == 42);
|
|
TuneAdvisor::resetConnectionLifetimeSecondsOverride();
|
|
}
|
|
|
|
TuneAdvisor::resetConnectionLifetimeSecondsOverride();
|
|
}
|
|
|
|
// =============================================================================
|
|
// Threshold Ordering Invariant Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("Threshold ordering invariant: warning < critical < emergency",
|
|
"[daemon][tune][advisor][catch2]") {
|
|
resetEvictionOverrides();
|
|
|
|
SECTION("Efficient profile maintains ordering") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() <
|
|
TuneAdvisor::modelEvictCriticalThreshold());
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() <
|
|
TuneAdvisor::modelEvictEmergencyThreshold());
|
|
}
|
|
|
|
SECTION("Balanced profile maintains ordering") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() <
|
|
TuneAdvisor::modelEvictCriticalThreshold());
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() <
|
|
TuneAdvisor::modelEvictEmergencyThreshold());
|
|
}
|
|
|
|
SECTION("Aggressive profile maintains ordering") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
|
|
CHECK(TuneAdvisor::modelEvictWarningThreshold() <
|
|
TuneAdvisor::modelEvictCriticalThreshold());
|
|
CHECK(TuneAdvisor::modelEvictCriticalThreshold() <
|
|
TuneAdvisor::modelEvictEmergencyThreshold());
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Repair Batch Size Profile Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("Repair batch sizes are profile-aware", "[daemon][tune][advisor][catch2]") {
|
|
SECTION("Efficient profile uses smaller batch sizes") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
|
|
// Efficient profile scale is 0.0
|
|
// repairMaxBatch: 8 + 24*0.0 = 8
|
|
// repairStartupBatchSize: 25 + 75*0.0 = 25
|
|
CHECK(TuneAdvisor::repairMaxBatch() == 8u);
|
|
CHECK(TuneAdvisor::repairStartupBatchSize() == 25u);
|
|
}
|
|
|
|
SECTION("Balanced profile uses default batch sizes") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
|
|
// Balanced profile scale is 0.5
|
|
// repairMaxBatch: 8 + 24*0.5 = 20
|
|
// repairStartupBatchSize: 25 + 75*0.5 = 62
|
|
CHECK(TuneAdvisor::repairMaxBatch() == 20u);
|
|
CHECK(TuneAdvisor::repairStartupBatchSize() == 62u);
|
|
}
|
|
|
|
SECTION("Aggressive profile uses larger batch sizes") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
|
|
// Aggressive profile scale is 1.0
|
|
// repairMaxBatch: 8 + 24*1.0 = 32
|
|
// repairStartupBatchSize: 25 + 75*1.0 = 100
|
|
CHECK(TuneAdvisor::repairMaxBatch() == 32u);
|
|
CHECK(TuneAdvisor::repairStartupBatchSize() == 100u);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Repair batch size env var overrides", "[daemon][tune][advisor][catch2]") {
|
|
ProfileGuard profileGuard(TuneAdvisor::Profile::Balanced);
|
|
|
|
SECTION("YAMS_REPAIR_MAX_BATCH env var overrides profile") {
|
|
ScopedEnvVar envGuard("YAMS_REPAIR_MAX_BATCH", "64");
|
|
|
|
CHECK(TuneAdvisor::repairMaxBatch() == 64u);
|
|
}
|
|
|
|
SECTION("YAMS_REPAIR_STARTUP_BATCH env var overrides profile") {
|
|
ScopedEnvVar envGuard("YAMS_REPAIR_STARTUP_BATCH", "200");
|
|
|
|
CHECK(TuneAdvisor::repairStartupBatchSize() == 200u);
|
|
}
|
|
|
|
SECTION("Invalid env var falls back to profile default") {
|
|
ScopedEnvVar envGuard("YAMS_REPAIR_MAX_BATCH", "invalid");
|
|
|
|
// Should fall back to profile default (20 for Balanced at 0.5 scale)
|
|
CHECK(TuneAdvisor::repairMaxBatch() == 20u);
|
|
}
|
|
|
|
SECTION("Zero env var falls back to profile default") {
|
|
ScopedEnvVar envGuard("YAMS_REPAIR_MAX_BATCH", "0");
|
|
|
|
// 0 is rejected, should fall back to profile default
|
|
CHECK(TuneAdvisor::repairMaxBatch() == 20u);
|
|
}
|
|
|
|
SECTION("Env var > 1000 falls back to profile default") {
|
|
ScopedEnvVar envGuard("YAMS_REPAIR_MAX_BATCH", "1001");
|
|
|
|
// > 1000 is rejected, should fall back to profile default
|
|
CHECK(TuneAdvisor::repairMaxBatch() == 20u);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Embed doc cap resolves to safe default when unset",
|
|
"[daemon][tune][advisor][embed][catch2]") {
|
|
struct EmbedCapGuard {
|
|
std::size_t prev{TuneAdvisor::getEmbedDocCap()};
|
|
~EmbedCapGuard() { TuneAdvisor::setEmbedDocCap(prev); }
|
|
} guard;
|
|
|
|
SECTION("Unset cap uses built-in default (no singleton fallback)") {
|
|
// Ensure no explicit override is active.
|
|
TuneAdvisor::setEmbedDocCap(0);
|
|
// Invalid env value should be ignored and still resolve to default.
|
|
ScopedEnvVar envGuard("YAMS_EMBED_DOC_CAP", "0");
|
|
CHECK(TuneAdvisor::getEmbedDocCap() == 0u);
|
|
CHECK(TuneAdvisor::resolvedEmbedDocCap() == TuneAdvisor::kDefaultEmbedDocCap);
|
|
CHECK(TuneAdvisor::resolvedEmbedDocCap() == 64u);
|
|
}
|
|
|
|
SECTION("Explicit cap overrides default") {
|
|
TuneAdvisor::setEmbedDocCap(128);
|
|
CHECK(TuneAdvisor::resolvedEmbedDocCap() == 128u);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Worker poll cadence overrides
|
|
// =============================================================================
|
|
|
|
TEST_CASE("Worker poll cadence overrides", "[daemon][tune][advisor][catch2]") {
|
|
SECTION("Dynamic updates apply when not pinned") {
|
|
ScopedEnvVar envGuard("YAMS_WORKER_POLL_MS", "0");
|
|
TuneAdvisor::setWorkerPollMs(0);
|
|
|
|
TuneAdvisor::setWorkerPollMsDynamic(200);
|
|
CHECK(TuneAdvisor::workerPollMs() == 200u);
|
|
|
|
TuneAdvisor::setWorkerPollMs(0);
|
|
TuneAdvisor::setWorkerPollMsDynamic(120);
|
|
CHECK(TuneAdvisor::workerPollMs() == 120u);
|
|
}
|
|
|
|
SECTION("Dynamic updates ignored when pinned by setter") {
|
|
ScopedEnvVar envGuard("YAMS_WORKER_POLL_MS", "0");
|
|
TuneAdvisor::setWorkerPollMs(250);
|
|
CHECK(TuneAdvisor::workerPollMs() == 250u);
|
|
|
|
TuneAdvisor::setWorkerPollMsDynamic(100);
|
|
CHECK(TuneAdvisor::workerPollMs() == 250u);
|
|
|
|
TuneAdvisor::setWorkerPollMs(0);
|
|
}
|
|
|
|
SECTION("Dynamic updates ignored when env pins cadence") {
|
|
ScopedEnvVar envGuard("YAMS_WORKER_POLL_MS", "300");
|
|
TuneAdvisor::setWorkerPollMs(0);
|
|
|
|
TuneAdvisor::setWorkerPollMsDynamic(100);
|
|
CHECK(TuneAdvisor::workerPollMs() == 300u);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Model maintenance thresholds use bounded environment overrides",
|
|
"[daemon][tune][advisor][catch2]") {
|
|
ProfileGuard profile(TuneAdvisor::Profile::Balanced);
|
|
ScopedEnvVar connectionThreshold("YAMS_MODEL_MAINT_CONN_THRESHOLD", "17");
|
|
ScopedEnvVar searchThreshold("YAMS_MODEL_MAINT_SEARCH_THRESHOLD", "19");
|
|
ScopedEnvVar queueThreshold("YAMS_MODEL_MAINT_QUEUE_THRESHOLD", "23");
|
|
|
|
CHECK(TuneAdvisor::modelMaintenanceConnThreshold() == 17);
|
|
CHECK(TuneAdvisor::modelMaintenanceSearchThreshold() == 19);
|
|
CHECK(TuneAdvisor::modelMaintenanceQueueThreshold() == 23);
|
|
|
|
connectionThreshold.set("101");
|
|
searchThreshold.set("101");
|
|
queueThreshold.set("10001");
|
|
CHECK(TuneAdvisor::modelMaintenanceConnThreshold() == 1);
|
|
CHECK(TuneAdvisor::modelMaintenanceSearchThreshold() == 1);
|
|
CHECK(TuneAdvisor::modelMaintenanceQueueThreshold() == 10);
|
|
}
|
|
|
|
TEST_CASE("Default-enabled tuning flags use explicit boolean overrides",
|
|
"[daemon][tune][advisor][catch2]") {
|
|
ScopedEnvVar proactiveEviction("YAMS_PROACTIVE_EVICTION", "off");
|
|
ScopedEnvVar semanticBackfill("YAMS_ENABLE_SEMANTIC_NEIGHBOR_BACKFILL", "no");
|
|
ScopedEnvVar gradientLimiters("YAMS_ENABLE_GRADIENT_LIMITERS", "false");
|
|
|
|
CHECK_FALSE(TuneAdvisor::enableProactiveEviction());
|
|
CHECK_FALSE(TuneAdvisor::enableSemanticNeighborBackfill());
|
|
CHECK_FALSE(TuneAdvisor::enableGradientLimiters());
|
|
|
|
proactiveEviction.set("invalid");
|
|
semanticBackfill.set("invalid");
|
|
gradientLimiters.set("invalid");
|
|
CHECK(TuneAdvisor::enableProactiveEviction());
|
|
CHECK(TuneAdvisor::enableSemanticNeighborBackfill());
|
|
CHECK(TuneAdvisor::enableGradientLimiters());
|
|
}
|
|
|
|
// =============================================================================
|
|
// GPU Detection Tests (Apple Silicon sysctl path)
|
|
// =============================================================================
|
|
|
|
TEST_CASE("detectGpu returns consistent cached result", "[daemon][gpu][catch2]") {
|
|
using namespace yams::daemon::resource;
|
|
|
|
const auto& first = detectGpu();
|
|
const auto& second = detectGpu();
|
|
|
|
// std::call_once caching: same static object returned each time
|
|
CHECK(&first == &second);
|
|
|
|
// Basic structural sanity: fields are default-initialized or populated
|
|
CHECK(first.detected == second.detected);
|
|
CHECK(first.name == second.name);
|
|
CHECK(first.vramBytes == second.vramBytes);
|
|
CHECK(first.provider == second.provider);
|
|
CHECK(first.unifiedMemory == second.unifiedMemory);
|
|
}
|
|
|
|
TEST_CASE("effectiveGpuBatchBudgetBytes applies unified-memory safety defaults",
|
|
"[daemon][gpu][batch-budget][catch2]") {
|
|
using namespace yams::daemon::resource;
|
|
|
|
constexpr uint64_t GiB = 1024ULL * 1024ULL * 1024ULL;
|
|
|
|
SECTION("Dedicated memory uses full detected budget by default") {
|
|
ScopedEnvVar clear1("YAMS_GPU_BATCH_BUDGET_MB", "");
|
|
ScopedEnvVar clear2("YAMS_GPU_UNIFIED_BUDGET_MB", "");
|
|
ScopedEnvVar clear3("YAMS_GPU_UNIFIED_BUDGET_FRACTION", "");
|
|
|
|
GpuInfo gpu;
|
|
gpu.detected = true;
|
|
gpu.unifiedMemory = false;
|
|
gpu.provider = "migraphx";
|
|
gpu.vramBytes = 12ULL * GiB;
|
|
|
|
CHECK(effectiveGpuBatchBudgetBytes(gpu) == 12ULL * GiB);
|
|
}
|
|
|
|
SECTION("Unified memory defaults to conservative 4 GiB cap") {
|
|
ScopedEnvVar clear1("YAMS_GPU_BATCH_BUDGET_MB", "");
|
|
ScopedEnvVar clear2("YAMS_GPU_UNIFIED_BUDGET_MB", "");
|
|
ScopedEnvVar clear3("YAMS_GPU_UNIFIED_BUDGET_FRACTION", "");
|
|
|
|
GpuInfo gpu;
|
|
gpu.detected = true;
|
|
gpu.unifiedMemory = true;
|
|
gpu.provider = "coreml";
|
|
gpu.vramBytes = 24ULL * GiB;
|
|
|
|
CHECK(effectiveGpuBatchBudgetBytes(gpu) == 4ULL * GiB);
|
|
}
|
|
|
|
SECTION("Unified memory MB override is honored") {
|
|
ScopedEnvVar clear1("YAMS_GPU_BATCH_BUDGET_MB", "");
|
|
ScopedEnvVar envMb("YAMS_GPU_UNIFIED_BUDGET_MB", "6144");
|
|
ScopedEnvVar clear3("YAMS_GPU_UNIFIED_BUDGET_FRACTION", "");
|
|
|
|
GpuInfo gpu;
|
|
gpu.detected = true;
|
|
gpu.unifiedMemory = true;
|
|
gpu.provider = "coreml";
|
|
gpu.vramBytes = 24ULL * GiB;
|
|
|
|
CHECK(effectiveGpuBatchBudgetBytes(gpu) == 6ULL * GiB);
|
|
}
|
|
|
|
SECTION("Global budget override takes precedence") {
|
|
ScopedEnvVar envGlobal("YAMS_GPU_BATCH_BUDGET_MB", "2048");
|
|
ScopedEnvVar envMb("YAMS_GPU_UNIFIED_BUDGET_MB", "6144");
|
|
ScopedEnvVar envFrac("YAMS_GPU_UNIFIED_BUDGET_FRACTION", "0.5");
|
|
|
|
GpuInfo gpu;
|
|
gpu.detected = true;
|
|
gpu.unifiedMemory = true;
|
|
gpu.provider = "coreml";
|
|
gpu.vramBytes = 24ULL * GiB;
|
|
|
|
CHECK(effectiveGpuBatchBudgetBytes(gpu) == 2ULL * GiB);
|
|
}
|
|
}
|
|
|
|
#if defined(__APPLE__) && defined(__aarch64__)
|
|
TEST_CASE("detectAppleSiliconGpu populates all fields", "[daemon][gpu][catch2]") {
|
|
using namespace yams::daemon::resource;
|
|
|
|
GpuInfo info;
|
|
bool result = detail::detectAppleSiliconGpu(info);
|
|
|
|
CHECK(result == true);
|
|
CHECK(info.detected == true);
|
|
CHECK(info.provider == "coreml");
|
|
CHECK(info.unifiedMemory == true);
|
|
|
|
// Brand string should contain "Apple" (e.g. "Apple M3 Max")
|
|
CHECK(info.name.find("Apple") != std::string::npos);
|
|
|
|
// VRAM should be populated
|
|
CHECK(info.vramBytes > 0);
|
|
|
|
// Cross-check: read hw.memsize via sysctl, verify vramBytes ≈ 75% of memsize
|
|
uint64_t memsize = 0;
|
|
size_t len = sizeof(memsize);
|
|
REQUIRE(detail::sysctlbyname("hw.memsize", &memsize, &len, nullptr, 0) == 0);
|
|
REQUIRE(memsize > 0);
|
|
|
|
uint64_t expected = static_cast<uint64_t>(static_cast<double>(memsize) * 0.75);
|
|
double ratio = static_cast<double>(info.vramBytes) / static_cast<double>(expected);
|
|
CHECK(ratio == Catch::Approx(1.0).margin(0.01));
|
|
|
|
// Sanity bounds: > 1 GB and < 1 TB
|
|
constexpr uint64_t oneGB = 1024ULL * 1024ULL * 1024ULL;
|
|
constexpr uint64_t oneTB = 1024ULL * oneGB;
|
|
CHECK(info.vramBytes > oneGB);
|
|
CHECK(info.vramBytes < oneTB);
|
|
}
|
|
#endif // __APPLE__ && __aarch64__
|
|
|
|
#if defined(__APPLE__)
|
|
TEST_CASE("sysctlString returns values for valid keys", "[daemon][gpu][catch2]") {
|
|
using namespace yams::daemon::resource::detail;
|
|
|
|
CHECK_FALSE(sysctlString("hw.machine").empty());
|
|
CHECK_FALSE(sysctlString("machdep.cpu.brand_string").empty());
|
|
|
|
// Invalid key should return empty gracefully (no crash)
|
|
CHECK(sysctlString("totally.invalid.key.xyz").empty());
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#if defined(__APPLE__) && !defined(__aarch64__)
|
|
TEST_CASE("detectAppleSiliconGpu returns false on non-ARM64", "[daemon][gpu][catch2]") {
|
|
using namespace yams::daemon::resource;
|
|
|
|
GpuInfo info;
|
|
bool result = detail::detectAppleSiliconGpu(info);
|
|
|
|
CHECK(result == false);
|
|
CHECK(info.detected == false);
|
|
}
|
|
#endif // __APPLE__ && !__aarch64__
|
|
|
|
// =============================================================================
|
|
// PostIngestQueue Profile-Aware Concurrency Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("PostIngestQueue methods are profile-aware", "[daemon][tune][advisor][catch2]") {
|
|
resetEvictionOverrides();
|
|
|
|
auto setHardwareConcurrency = [](unsigned value) {
|
|
yams::daemon::TuneAdvisor::setHardwareConcurrencyForTests(value);
|
|
};
|
|
|
|
SECTION("Efficient profile uses 0.0 scaling") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
ScopedEnvVar maxThreadsGuard("YAMS_MAX_THREADS", "0");
|
|
ScopedEnvVar postIngestGuard("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
|
|
setHardwareConcurrency(8);
|
|
|
|
// With hw=8, all 6 stages active: totalBudget = max(formula, 6) = 6.
|
|
// Each stage gets exactly 1 slot (budget distributed evenly).
|
|
CHECK(TuneAdvisor::postExtractionConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postKgConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postSymbolConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEntityConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postTitleConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEmbedConcurrent() == 1u);
|
|
}
|
|
|
|
SECTION("Balanced profile uses 0.5 scaling") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
ScopedEnvVar maxThreadsGuard("YAMS_MAX_THREADS", "0");
|
|
ScopedEnvVar postIngestGuard("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
|
|
setHardwareConcurrency(8);
|
|
|
|
// With hw=8, all 6 stages active: totalBudget = max(3, 6) = 6.
|
|
// Each stage gets exactly 1 slot.
|
|
CHECK(TuneAdvisor::postExtractionConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postKgConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postSymbolConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEntityConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postTitleConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEmbedConcurrent() == 1u);
|
|
}
|
|
|
|
SECTION("Aggressive profile uses 1.0x (defaults)") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
ScopedEnvVar maxThreadsGuard("YAMS_MAX_THREADS", "0");
|
|
ScopedEnvVar postIngestGuard("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
|
|
setHardwareConcurrency(8);
|
|
|
|
// With hw=8, round-up division: base=max(2,2)=2, scaleRange=max(1,2)=2.
|
|
// total=2+2=4. But 6 active stages, total=max(4,6)=6. Each gets 1.
|
|
CHECK(TuneAdvisor::postExtractionConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postKgConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postSymbolConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEntityConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postTitleConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEmbedConcurrent() == 1u);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// statusTickMs Default and Override Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("statusTickMs defaults to 5", "[daemon][governance][catch2]") {
|
|
CHECK(TuneAdvisor::statusTickMs() == 5);
|
|
}
|
|
|
|
TEST_CASE("statusTickMs remains fixed at 5ms", "[daemon][governance][catch2]") {
|
|
CHECK(TuneAdvisor::statusTickMs() == 5);
|
|
}
|
|
|
|
TEST_CASE("Thread reserve is profile-aware", "[daemon][governance][catch2]") {
|
|
for (unsigned hw : {8u, 16u}) {
|
|
const auto effReserve = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
return TuneAdvisor::testing_reservedHostThreads(hw);
|
|
}();
|
|
const auto balReserve = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return TuneAdvisor::testing_reservedHostThreads(hw);
|
|
}();
|
|
const auto aggReserve = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
return TuneAdvisor::testing_reservedHostThreads(hw);
|
|
}();
|
|
|
|
INFO("hw=" << hw << " eff=" << effReserve << " bal=" << balReserve
|
|
<< " agg=" << aggReserve);
|
|
CHECK(effReserve >= balReserve);
|
|
CHECK(balReserve >= aggReserve);
|
|
CHECK(effReserve < hw);
|
|
CHECK(aggReserve < hw);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("recommendedThreads remains profile-aware while leaving host headroom",
|
|
"[daemon][governance][catch2]") {
|
|
ScopedEnvVar maxThreadsGuard("YAMS_MAX_THREADS", "0");
|
|
HardwareGuard hwGuard(16);
|
|
|
|
const auto effThreads = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
return TuneAdvisor::recommendedThreads(1.0);
|
|
}();
|
|
const auto balThreads = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return TuneAdvisor::recommendedThreads(1.0);
|
|
}();
|
|
const auto aggThreads = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
return TuneAdvisor::recommendedThreads(1.0);
|
|
}();
|
|
|
|
INFO("eff=" << effThreads << " bal=" << balThreads << " agg=" << aggThreads);
|
|
CHECK(effThreads <= balThreads);
|
|
CHECK(balThreads <= aggThreads);
|
|
CHECK(aggThreads < 16u);
|
|
CHECK(effThreads >= 1u);
|
|
}
|
|
|
|
TEST_CASE("Auto memory budget is profile-aware", "[daemon][governance][catch2]") {
|
|
constexpr uint64_t kSystemMem = 8ull * 1024ull * 1024ull * 1024ull;
|
|
|
|
const auto effBudget = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
return TuneAdvisor::testing_autoMemoryBudgetBytes(kSystemMem);
|
|
}();
|
|
const auto balBudget = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return TuneAdvisor::testing_autoMemoryBudgetBytes(kSystemMem);
|
|
}();
|
|
const auto aggBudget = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
return TuneAdvisor::testing_autoMemoryBudgetBytes(kSystemMem);
|
|
}();
|
|
|
|
INFO("eff=" << effBudget << " bal=" << balBudget << " agg=" << aggBudget);
|
|
CHECK(effBudget < balBudget);
|
|
CHECK(balBudget < aggBudget);
|
|
CHECK(aggBudget < kSystemMem);
|
|
CHECK(effBudget == (kSystemMem * 45ull) / 100ull);
|
|
CHECK(balBudget == (kSystemMem * 60ull) / 100ull);
|
|
CHECK(aggBudget == (kSystemMem * 75ull) / 100ull);
|
|
}
|
|
|
|
TEST_CASE("WorkCoordinator defaults are io-biased", "[daemon][governance][catch2]") {
|
|
ScopedEnvVar workThreadsGuard("YAMS_WORK_COORDINATOR_THREADS", "0");
|
|
HardwareGuard hwGuard(16);
|
|
|
|
const auto efficientWorkers = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
return TuneAdvisor::workCoordinatorThreads();
|
|
}();
|
|
const auto balancedWorkers = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return TuneAdvisor::workCoordinatorThreads();
|
|
}();
|
|
const auto aggressiveWorkers = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
return TuneAdvisor::workCoordinatorThreads();
|
|
}();
|
|
|
|
const auto efficientExpectedWorkers = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
return std::max(4u, TuneAdvisor::recommendedThreads(1.0));
|
|
}();
|
|
const auto balancedExpectedWorkers = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return std::max(4u, TuneAdvisor::recommendedThreads(1.5));
|
|
}();
|
|
const auto aggressiveExpectedWorkers = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
return std::max(4u, TuneAdvisor::recommendedThreads(1.75));
|
|
}();
|
|
|
|
CHECK(efficientWorkers == efficientExpectedWorkers);
|
|
CHECK(balancedWorkers == balancedExpectedWorkers);
|
|
CHECK(aggressiveWorkers == aggressiveExpectedWorkers);
|
|
CHECK(efficientWorkers <= balancedWorkers);
|
|
CHECK(balancedWorkers <= aggressiveWorkers);
|
|
}
|
|
|
|
TEST_CASE("Search concurrency limit has a balanced floor", "[daemon][governance][catch2]") {
|
|
ScopedEnvVar searchOverrideGuard("YAMS_SEARCH_MAX_CONCURRENT", "0");
|
|
HardwareGuard hwGuard(10);
|
|
|
|
const auto efficientLimit = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
return TuneAdvisor::searchConcurrencyLimit();
|
|
}();
|
|
const auto balancedLimit = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return TuneAdvisor::searchConcurrencyLimit();
|
|
}();
|
|
const auto aggressiveLimit = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
return TuneAdvisor::searchConcurrencyLimit();
|
|
}();
|
|
|
|
const auto efficientExpected = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
return std::max<uint32_t>(2, TuneAdvisor::recommendedThreads(0.5)) * 2;
|
|
}();
|
|
const auto balancedExpected = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return std::max<uint32_t>(5u,
|
|
std::max<uint32_t>(2, TuneAdvisor::recommendedThreads(0.5)) * 2);
|
|
}();
|
|
const auto aggressiveExpected = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
return std::max<uint32_t>(6u,
|
|
std::max<uint32_t>(2, TuneAdvisor::recommendedThreads(0.5)) * 2);
|
|
}();
|
|
|
|
CHECK(efficientLimit == efficientExpected);
|
|
CHECK(balancedLimit == balancedExpected);
|
|
CHECK(aggressiveLimit == aggressiveExpected);
|
|
}
|
|
|
|
TEST_CASE("Read pool max connections follow centralized capacity model",
|
|
"[daemon][governance][catch2]") {
|
|
HardwareGuard hwGuard(16);
|
|
|
|
const auto efficientCap = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
return TuneAdvisor::readPoolMaxConnections(32);
|
|
}();
|
|
const auto balancedCap = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return TuneAdvisor::readPoolMaxConnections(32);
|
|
}();
|
|
const auto aggressiveCap = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
return TuneAdvisor::readPoolMaxConnections(32);
|
|
}();
|
|
|
|
const auto efficientExpected = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
const auto derived = std::max<uint32_t>({4u, TuneAdvisor::workCoordinatorThreads(),
|
|
TuneAdvisor::searchConcurrencyLimit(),
|
|
TuneAdvisor::listInflightLimit()});
|
|
return std::min<uint32_t>(8u, derived);
|
|
}();
|
|
const auto balancedExpected = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
const auto derived = std::max<uint32_t>({4u, TuneAdvisor::workCoordinatorThreads(),
|
|
TuneAdvisor::searchConcurrencyLimit(),
|
|
TuneAdvisor::listInflightLimit()});
|
|
return std::min<uint32_t>(8u, derived);
|
|
}();
|
|
const auto aggressiveExpected = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
const auto derived = std::max<uint32_t>({4u, TuneAdvisor::workCoordinatorThreads(),
|
|
TuneAdvisor::searchConcurrencyLimit(),
|
|
TuneAdvisor::listInflightLimit()});
|
|
return std::min<uint32_t>(8u, derived);
|
|
}();
|
|
|
|
CHECK(efficientCap == efficientExpected);
|
|
CHECK(balancedCap == balancedExpected);
|
|
CHECK(aggressiveCap == aggressiveExpected);
|
|
|
|
const auto cappedConfiguredMax = [&]() {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
return TuneAdvisor::readPoolMaxConnections(6);
|
|
}();
|
|
CHECK(cappedConfiguredMax <= 6u);
|
|
CHECK(cappedConfiguredMax >= 1u);
|
|
}
|
|
|
|
// =============================================================================
|
|
// computeCpuThrottleDelayMs Tests
|
|
// =============================================================================
|
|
|
|
TEST_CASE("computeCpuThrottleDelayMs returns 0 below threshold", "[daemon][governance][catch2]") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Balanced);
|
|
// Balanced threshold = 50 + 0.5*35 = 67.5%
|
|
CHECK(TuneAdvisor::computeCpuThrottleDelayMs(60.0) == 0);
|
|
CHECK(TuneAdvisor::computeCpuThrottleDelayMs(0.0) == 0);
|
|
}
|
|
|
|
TEST_CASE("computeCpuThrottleDelayMs returns clamped delay above threshold",
|
|
"[daemon][governance][catch2]") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Aggressive);
|
|
// Aggressive threshold = 50 + 1.0*35 = 85%
|
|
double threshold = TuneAdvisor::cpuHighThresholdPercent();
|
|
int32_t delay = TuneAdvisor::computeCpuThrottleDelayMs(threshold + 10.0);
|
|
CHECK(delay >= 2);
|
|
CHECK(delay <= 25);
|
|
}
|
|
|
|
TEST_CASE("computeCpuThrottleDelayMs clamps to max 25ms", "[daemon][governance][catch2]") {
|
|
// Even at 100% CPU, delay should not exceed 25ms
|
|
int32_t delay = TuneAdvisor::computeCpuThrottleDelayMs(100.0);
|
|
CHECK(delay <= 25);
|
|
}
|
|
|
|
TEST_CASE("cpuHighThresholdPercent setter overrides profile and env",
|
|
"[daemon][governance][catch2]") {
|
|
TuneAdvisor::resetCpuHighThresholdPercentOverride();
|
|
|
|
SECTION("setter overrides profile default") {
|
|
ProfileGuard guard(TuneAdvisor::Profile::Efficient);
|
|
TuneAdvisor::setCpuHighThresholdPercent(72.0);
|
|
CHECK(TuneAdvisor::cpuHighThresholdPercent() == Catch::Approx(72.0));
|
|
TuneAdvisor::resetCpuHighThresholdPercentOverride();
|
|
}
|
|
|
|
SECTION("setter overrides env var") {
|
|
ScopedEnvVar envGuard("YAMS_CPU_HIGH_PCT", "55");
|
|
CHECK(TuneAdvisor::cpuHighThresholdPercent() == Catch::Approx(55.0));
|
|
TuneAdvisor::setCpuHighThresholdPercent(68.0);
|
|
CHECK(TuneAdvisor::cpuHighThresholdPercent() == Catch::Approx(68.0));
|
|
TuneAdvisor::resetCpuHighThresholdPercentOverride();
|
|
}
|
|
}
|