icinga2/test/perfdata-perfdatawriterfixture.hpp
Johannes Schmidt 503e23e723 Freeze perfdata arrays and remove locks in code using them
Since perfdata is set once when a check result is created and
never changed again, locking this is unnecessary. This avoids
components unnecessarily waiting on each other when processing
perfdata.

This fixes the locking cascade observed sometimes when the perfdata
writer work queue blocks, where it extends to a lock on the entire
check result eventually, affecting even more components.
2026-06-17 15:15:46 +02:00

139 lines
3.7 KiB
C++

// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <BoostTestTargetConfig.h>
#include "base/perfdatavalue.hpp"
#include "config/configcompiler.hpp"
#include "config/configitem.hpp"
#include "icinga/host.hpp"
#include "test/base-testloggerfixture.hpp"
#include "test/perfdata-perfdatatargetfixture.hpp"
#include "test/utils.hpp"
#include <boost/hana.hpp>
namespace icinga {
template<typename Writer>
class PerfdataWriterFixture : public PerfdataWriterTargetFixture, public TestLoggerFixture
{
public:
PerfdataWriterFixture() : m_Writer(new Writer)
{
auto createObjects = [&]() {
String config = R"CONFIG(
object CheckCommand "dummy" {
command = "/bin/echo"
}
object Host "h1" {
address = "h1"
check_command = "dummy"
enable_notifications = true
enable_active_checks = false
enable_passive_checks = true
}
)CONFIG";
std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("<test>", config);
expr->Evaluate(*ScriptFrame::GetCurrentFrame());
};
ConfigItem::RunWithActivationContext(new Function("CreateTestObjects", createObjects));
m_Host = Host::GetByName("h1");
BOOST_REQUIRE(m_Host);
m_Writer->SetPort(std::to_string(GetPort()));
m_Writer->SetName(m_Writer->GetReflectionType()->GetName());
m_Writer->SetDisconnectTimeout(0.05);
m_Writer->Register();
auto hasFlushInterval = boost::hana::is_valid([](auto&& obj) -> decltype(obj.SetFlushInterval(0.05)) {});
if constexpr (decltype(hasFlushInterval(std::declval<Writer>()))::value) {
m_Writer->SetFlushInterval(0.05);
}
auto hasFlushThreshold = boost::hana::is_valid([](auto&& obj) -> decltype(obj.SetFlushThreshold(1)) {});
if constexpr (decltype(hasFlushThreshold(std::declval<Writer>()))::value) {
m_Writer->SetFlushThreshold(1);
}
}
void ReceiveCheckResults(
std::size_t num,
ServiceState state,
const std::function<void(const CheckResult::Ptr&)>& fn = {}
)
{
::ReceiveCheckResults(m_Host, num, state, fn);
}
std::size_t GetWorkQueueLength()
{
Array::Ptr dummy = new Array;
Dictionary::Ptr status = new Dictionary;
m_Writer->StatsFunc(status, dummy);
ObjectLock lock{status};
// Unpack the single-key top-level dictionary
Dictionary::Ptr writer = status->Begin()->second;
BOOST_REQUIRE(writer);
Dictionary::Ptr values = writer->Get(m_Writer->GetName());
BOOST_REQUIRE(values);
BOOST_REQUIRE(values->Contains("work_queue_items"));
return values->Get("work_queue_items");
}
/**
* Processes check results until the writer's work queue is no longer moving.
*
* @param timeout Time after which to give up trying to get the writer stuck
* @return true if the writer is now stuck
*/
bool GetWriterStuck(std::chrono::milliseconds timeout)
{
auto start = std::chrono::steady_clock::now();
std::size_t unchangedCount = 0;
while(true){
ReceiveCheckResults(10, ServiceCritical, [&](const CheckResult::Ptr& cr) {
cr->SetPerformanceData(new Array{new PerfdataValue{GetRandomString("", 4096), 1}});
});
if (std::chrono::steady_clock::now() - start >= timeout) {
return false;
}
auto numWq = GetWorkQueueLength();
if (numWq >= 10) {
std::this_thread::sleep_for(1ms);
if (numWq == GetWorkQueueLength()) {
if (unchangedCount < 5) {
++unchangedCount;
continue;
}
return true;
}
unchangedCount = 0;
}
}
}
void ResumeWriter()
{
static_cast<ConfigObject::Ptr>(m_Writer)->OnConfigLoaded();
m_Writer->SetActive(true);
m_Writer->Activate();
BOOST_REQUIRE(!m_Writer->IsPaused());
}
void PauseWriter() { static_cast<ConfigObject::Ptr>(m_Writer)->Pause(); }
auto GetWriter() { return m_Writer; }
private:
Host::Ptr m_Host;
typename Writer::Ptr m_Writer;
};
} // namespace icinga