Added three new integration test scenarios: - Test 08: Config Hot-Reload (dynamic configuration updates) - Test 09: Module Dependencies (dependency injection & cascade reload) - Test 10: Multi-Version Coexistence (canary deployment & progressive migration) Fixes: - Fixed CTest working directory for all tests (add WORKING_DIRECTORY) - Fixed module paths to use relative paths (./ prefix) - Fixed IModule.h comments for clarity New test modules: - ConfigurableModule (for config reload testing) - BaseModule, DependentModule, IndependentModule (for dependency testing) - GameLogicModuleV1/V2/V3 (for multi-version testing) Test coverage now includes 10 comprehensive integration scenarios covering hot-reload, chaos testing, stress testing, race conditions, memory leaks, error recovery, limits, config reload, dependencies, and multi-versioning. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
143 lines
6.1 KiB
C++
143 lines
6.1 KiB
C++
// ============================================================================
|
|
// profile_memory_leak.cpp - Detailed memory profiling for leak detection
|
|
// ============================================================================
|
|
|
|
#include "grove/ModuleLoader.h"
|
|
#include "grove/SequentialModuleSystem.h"
|
|
#include "grove/JsonDataNode.h"
|
|
#include "helpers/SystemUtils.h"
|
|
#include <spdlog/spdlog.h>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <filesystem>
|
|
#include <thread>
|
|
|
|
using namespace grove;
|
|
namespace fs = std::filesystem;
|
|
|
|
void printMemory(const std::string& label) {
|
|
size_t mem = getCurrentMemoryUsage();
|
|
float mb = mem / (1024.0f * 1024.0f);
|
|
std::cout << std::setw(40) << std::left << label << ": "
|
|
<< std::fixed << std::setprecision(3) << mb << " MB\n";
|
|
}
|
|
|
|
int main() {
|
|
std::cout << "================================================================================\n";
|
|
std::cout << "MEMORY LEAK PROFILER - Detailed Analysis\n";
|
|
std::cout << "================================================================================\n\n";
|
|
|
|
fs::path modulePath = "build/tests/libLeakTestModule.so";
|
|
if (!fs::exists(modulePath)) {
|
|
std::cerr << "❌ Module not found: " << modulePath << "\n";
|
|
return 1;
|
|
}
|
|
|
|
// Disable verbose logging
|
|
spdlog::set_level(spdlog::level::err);
|
|
|
|
printMemory("1. Initial baseline");
|
|
|
|
// Create loader and system
|
|
ModuleLoader loader;
|
|
printMemory("2. After ModuleLoader creation");
|
|
|
|
auto moduleSystem = std::make_unique<SequentialModuleSystem>();
|
|
moduleSystem->setLogLevel(spdlog::level::err);
|
|
printMemory("3. After ModuleSystem creation");
|
|
|
|
// Initial load
|
|
{
|
|
auto module = loader.load(modulePath, "LeakTestModule", false);
|
|
printMemory("4. After initial load");
|
|
|
|
nlohmann::json configJson = nlohmann::json::object();
|
|
auto config = std::make_unique<JsonDataNode>("config", configJson);
|
|
module->setConfiguration(*config, nullptr, nullptr);
|
|
printMemory("5. After setConfiguration");
|
|
|
|
moduleSystem->registerModule("LeakTestModule", std::move(module));
|
|
printMemory("6. After registerModule");
|
|
}
|
|
|
|
std::cout << "\n--- Starting 10 reload cycles ---\n\n";
|
|
|
|
size_t baselineAfterFirstLoad = getCurrentMemoryUsage();
|
|
|
|
for (int i = 1; i <= 10; i++) {
|
|
std::cout << "=== Cycle " << i << " ===\n";
|
|
|
|
size_t memBefore = getCurrentMemoryUsage();
|
|
|
|
// Extract module
|
|
auto module = moduleSystem->extractModule();
|
|
size_t memAfterExtract = getCurrentMemoryUsage();
|
|
|
|
auto state = module->getState();
|
|
size_t memAfterGetState = getCurrentMemoryUsage();
|
|
|
|
auto config = std::make_unique<JsonDataNode>("config",
|
|
dynamic_cast<const JsonDataNode&>(module->getConfiguration()).getJsonData());
|
|
size_t memAfterGetConfig = getCurrentMemoryUsage();
|
|
|
|
// Destroy old module
|
|
module.reset();
|
|
size_t memAfterReset = getCurrentMemoryUsage();
|
|
|
|
// Reload
|
|
auto newModule = loader.load(modulePath, "LeakTestModule", true);
|
|
size_t memAfterLoad = getCurrentMemoryUsage();
|
|
|
|
// Restore
|
|
newModule->setConfiguration(*config, nullptr, nullptr);
|
|
size_t memAfterSetConfig = getCurrentMemoryUsage();
|
|
|
|
newModule->setState(*state);
|
|
size_t memAfterSetState = getCurrentMemoryUsage();
|
|
|
|
// Register
|
|
moduleSystem->registerModule("LeakTestModule", std::move(newModule));
|
|
size_t memAfterRegister = getCurrentMemoryUsage();
|
|
|
|
// Process once
|
|
moduleSystem->processModules(0.016f);
|
|
size_t memAfterProcess = getCurrentMemoryUsage();
|
|
|
|
// Analysis
|
|
auto toKB = [](size_t delta) { return delta / 1024.0f; };
|
|
|
|
std::cout << " extractModule: " << std::setw(8) << toKB(memAfterExtract - memBefore) << " KB\n";
|
|
std::cout << " getState: " << std::setw(8) << toKB(memAfterGetState - memAfterExtract) << " KB\n";
|
|
std::cout << " getConfiguration: " << std::setw(8) << toKB(memAfterGetConfig - memAfterGetState) << " KB\n";
|
|
std::cout << " module.reset: " << std::setw(8) << toKB(memAfterReset - memAfterGetConfig) << " KB\n";
|
|
std::cout << " loader.load: " << std::setw(8) << toKB(memAfterLoad - memAfterReset) << " KB\n";
|
|
std::cout << " setConfiguration: " << std::setw(8) << toKB(memAfterSetConfig - memAfterLoad) << " KB\n";
|
|
std::cout << " setState: " << std::setw(8) << toKB(memAfterSetState - memAfterSetConfig) << " KB\n";
|
|
std::cout << " registerModule: " << std::setw(8) << toKB(memAfterRegister - memAfterSetState) << " KB\n";
|
|
std::cout << " processModules: " << std::setw(8) << toKB(memAfterProcess - memAfterRegister) << " KB\n";
|
|
std::cout << " TOTAL THIS CYCLE: " << std::setw(8) << toKB(memAfterProcess - memBefore) << " KB\n";
|
|
|
|
size_t totalGrowth = memAfterProcess - baselineAfterFirstLoad;
|
|
std::cout << " Cumulative growth: " << std::setw(8) << toKB(totalGrowth) << " KB\n";
|
|
std::cout << "\n";
|
|
|
|
// Small delay to let system settle
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
size_t finalMem = getCurrentMemoryUsage();
|
|
float totalGrowthKB = (finalMem - baselineAfterFirstLoad) / 1024.0f;
|
|
float perCycleKB = totalGrowthKB / 10.0f;
|
|
|
|
std::cout << "\n================================================================================\n";
|
|
std::cout << "SUMMARY\n";
|
|
std::cout << "================================================================================\n";
|
|
std::cout << "Baseline after first load: " << (baselineAfterFirstLoad / 1024.0f / 1024.0f) << " MB\n";
|
|
std::cout << "Final memory: " << (finalMem / 1024.0f / 1024.0f) << " MB\n";
|
|
std::cout << "Total growth (10 cycles): " << totalGrowthKB << " KB\n";
|
|
std::cout << "Average per cycle: " << perCycleKB << " KB\n";
|
|
std::cout << "================================================================================\n";
|
|
|
|
return 0;
|
|
}
|