// TuneAdvisor model eviction threshold tests (Catch2) // Tests profile defaults, env var overrides, programmatic setters, and validation #include #include #include #include #include #include #include #include #include #include "../../common/env_compat.h" #include "../../common/test_helpers_catch2.h" #include 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 PostIngestStageActivityGuard { public: PostIngestStageActivityGuard() { try { for (; acquired_ < kStages.size(); ++acquired_) { tokens_[acquired_] = TuneAdvisor::acquirePostIngestStageActivity(kStages[acquired_]); } } catch (...) { releaseAcquired(); throw; } } ~PostIngestStageActivityGuard() { releaseAcquired(); } PostIngestStageActivityGuard(const PostIngestStageActivityGuard&) = delete; PostIngestStageActivityGuard& operator=(const PostIngestStageActivityGuard&) = delete; private: void releaseAcquired() noexcept { while (acquired_ > 0) { --acquired_; TuneAdvisor::releasePostIngestStageActivity(kStages[acquired_], tokens_[acquired_]); } } static constexpr std::array kStages{ TuneAdvisor::PostIngestStage::Extraction, TuneAdvisor::PostIngestStage::KnowledgeGraph, TuneAdvisor::PostIngestStage::Symbol, TuneAdvisor::PostIngestStage::Entity, TuneAdvisor::PostIngestStage::Title, TuneAdvisor::PostIngestStage::Embed, }; std::array tokens_{}; std::size_t acquired_{0}; }; 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::quiet_NaN()); CHECK(TuneAdvisor::modelEvictWarningThreshold() == Catch::Approx(prev)); } SECTION("Positive infinity is rejected") { double prev = TuneAdvisor::modelEvictCriticalThreshold(); TuneAdvisor::setModelEvictCriticalThreshold(std::numeric_limits::infinity()); CHECK(TuneAdvisor::modelEvictCriticalThreshold() == Catch::Approx(prev)); } SECTION("Negative infinity is rejected") { double prev = TuneAdvisor::modelEvictEmergencyThreshold(); TuneAdvisor::setModelEvictEmergencyThreshold(-std::numeric_limits::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(static_cast(memsize) * 0.75); double ratio = static_cast(info.vramBytes) / static_cast(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(); PostIngestStageActivityGuard activeStages; 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(2, TuneAdvisor::recommendedThreads(0.5)) * 2; }(); const auto balancedExpected = [&]() { ProfileGuard guard(TuneAdvisor::Profile::Balanced); return std::max(5u, std::max(2, TuneAdvisor::recommendedThreads(0.5)) * 2); }(); const auto aggressiveExpected = [&]() { ProfileGuard guard(TuneAdvisor::Profile::Aggressive); return std::max(6u, std::max(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({4u, TuneAdvisor::workCoordinatorThreads(), TuneAdvisor::searchConcurrencyLimit(), TuneAdvisor::listInflightLimit()}); return std::min(8u, derived); }(); const auto balancedExpected = [&]() { ProfileGuard guard(TuneAdvisor::Profile::Balanced); const auto derived = std::max({4u, TuneAdvisor::workCoordinatorThreads(), TuneAdvisor::searchConcurrencyLimit(), TuneAdvisor::listInflightLimit()}); return std::min(8u, derived); }(); const auto aggressiveExpected = [&]() { ProfileGuard guard(TuneAdvisor::Profile::Aggressive); const auto derived = std::max({4u, TuneAdvisor::workCoordinatorThreads(), TuneAdvisor::searchConcurrencyLimit(), TuneAdvisor::listInflightLimit()}); return std::min(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(); } }