#pragma once #include "callback.hpp" #include "data_types.hpp" #include #include 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 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& _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 values; for (const auto& item : index) { const auto& storage = std::get(item.second); // Don't count properties that don't exist. if (!std::get(storage.get()).has_value()) { continue; } values.emplace_back( std::any_cast(std::get(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 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