blob: 99769721a30c6443a9878dae57b3d5df3f7e47c8 [file] [log] [blame]
#pragma once
#include "callback.hpp"
#include "data_types.hpp"
#include <algorithm>
#include <functional>
namespace phosphor
{
namespace dbus
{
namespace monitoring
{
/** @class MedianCondition
* @brief Determine a median from properties and apply a condition.
*
* When invoked, a median class instance performs its condition
* test against a median value that has been determined from a set
* of configured properties.
*
* Once the median value is determined, a C++ relational operator
* is applied to it and a value provided by the configuration file,
* which determines if the condition passes or not.
*
* Where no property values configured are found to determine a median from,
* the condition defaults to `true` and passes.
*
* If the oneshot parameter is true, then this condition won't pass
* again until it fails at least once.
*/
template <typename T>
class MedianCondition : public IndexedConditional
{
public:
MedianCondition() = delete;
MedianCondition(const MedianCondition&) = default;
MedianCondition(MedianCondition&&) = default;
MedianCondition& operator=(const MedianCondition&) = default;
MedianCondition& operator=(MedianCondition&&) = default;
~MedianCondition() = default;
MedianCondition(const PropertyIndex& conditionIndex,
const std::function<bool(T)>& _medianOp,
bool oneshot = false) :
IndexedConditional(conditionIndex),
medianOp(_medianOp), oneshot(oneshot)
{}
bool operator()() override
{
// Default the condition result to true
// if no property values are found to produce a median.
auto result = true;
std::vector<T> values;
for (const auto& item : index)
{
const auto& storage = std::get<storageIndex>(item.second);
// Don't count properties that don't exist.
if (!std::get<valueIndex>(storage.get()).has_value())
{
continue;
}
values.emplace_back(
std::any_cast<T>(std::get<valueIndex>(storage.get())));
}
if (!values.empty())
{
auto median = values.front();
// Get the determined median value
if (values.size() == 2)
{
// For 2 values, use the highest instead of the average
// for a worst case median value
median = *std::max_element(values.begin(), values.end());
}
else if (values.size() > 2)
{
const auto oddIt = values.begin() + values.size() / 2;
std::nth_element(values.begin(), oddIt, values.end());
median = *oddIt;
// Determine median for even number of values
if (index.size() % 2 == 0)
{
// Use average of middle 2 values for median
const auto evenIt = values.begin() + values.size() / 2 - 1;
std::nth_element(values.begin(), evenIt, values.end());
median = (median + *evenIt) / 2;
}
}
// Now apply the condition to the median value.
result = medianOp(median);
}
// If this was a oneshot and the the condition has already
// passed, then don't let it pass again until the condition
// has gone back to false.
if (oneshot && result && lastResult)
{
return false;
}
lastResult = result;
return result;
}
private:
/** @brief The comparison to perform on the median value. */
std::function<bool(T)> medianOp;
/** @brief If the condition can be allowed to pass again
on subsequent checks that are also true. */
const bool oneshot;
/** @brief The result of the previous check. */
bool lastResult = false;
};
} // namespace monitoring
} // namespace dbus
} // namespace phosphor