1 #pragma once 2 3 #include "callback.hpp" 4 #include "data_types.hpp" 5 6 #include <algorithm> 7 #include <functional> 8 9 namespace phosphor 10 { 11 namespace dbus 12 { 13 namespace monitoring 14 { 15 16 /** @class MedianCondition 17 * @brief Determine a median from properties and apply a condition. 18 * 19 * When invoked, a median class instance performs its condition 20 * test against a median value that has been determined from a set 21 * of configured properties. 22 * 23 * Once the median value is determined, a C++ relational operator 24 * is applied to it and a value provided by the configuration file, 25 * which determines if the condition passes or not. 26 * 27 * Where no property values configured are found to determine a median from, 28 * the condition defaults to `true` and passes. 29 * 30 * If the oneshot parameter is true, then this condition won't pass 31 * again until it fails at least once. 32 */ 33 template <typename T> 34 class MedianCondition : public IndexedConditional 35 { 36 public: 37 MedianCondition() = delete; 38 MedianCondition(const MedianCondition&) = default; 39 MedianCondition(MedianCondition&&) = default; 40 MedianCondition& operator=(const MedianCondition&) = default; 41 MedianCondition& operator=(MedianCondition&&) = default; 42 ~MedianCondition() = default; 43 44 MedianCondition(const PropertyIndex& conditionIndex, 45 const std::function<bool(T)>& _medianOp, 46 bool oneshot = false) : 47 IndexedConditional(conditionIndex), 48 medianOp(_medianOp), oneshot(oneshot) 49 { 50 } 51 52 bool operator()() override 53 { 54 // Default the condition result to true 55 // if no property values are found to produce a median. 56 auto result = true; 57 std::vector<T> values; 58 for (const auto& item : index) 59 { 60 const auto& storage = std::get<storageIndex>(item.second); 61 // Don't count properties that don't exist. 62 if (std::get<valueIndex>(storage.get()).empty()) 63 { 64 continue; 65 } 66 values.emplace_back( 67 any_ns::any_cast<T>(std::get<valueIndex>(storage.get()))); 68 } 69 70 if (!values.empty()) 71 { 72 auto median = values.front(); 73 // Get the determined median value 74 if (values.size() == 2) 75 { 76 // For 2 values, use the highest instead of the average 77 // for a worst case median value 78 median = *std::max_element(values.begin(), values.end()); 79 } 80 else if (values.size() > 2) 81 { 82 const auto oddIt = values.begin() + values.size() / 2; 83 std::nth_element(values.begin(), oddIt, values.end()); 84 median = *oddIt; 85 // Determine median for even number of values 86 if (index.size() % 2 == 0) 87 { 88 // Use average of middle 2 values for median 89 const auto evenIt = values.begin() + values.size() / 2 - 1; 90 std::nth_element(values.begin(), evenIt, values.end()); 91 median = (median + *evenIt) / 2; 92 } 93 } 94 95 // Now apply the condition to the median value. 96 result = medianOp(median); 97 } 98 99 // If this was a oneshot and the the condition has already 100 // passed, then don't let it pass again until the condition 101 // has gone back to false. 102 if (oneshot && result && lastResult) 103 { 104 return false; 105 } 106 107 lastResult = result; 108 return result; 109 } 110 111 private: 112 /** @brief The comparison to perform on the median value. */ 113 std::function<bool(T)> medianOp; 114 /** @brief If the condition can be allowed to pass again 115 on subsequent checks that are also true. */ 116 const bool oneshot; 117 /** @brief The result of the previous check. */ 118 bool lastResult = false; 119 }; 120 121 } // namespace monitoring 122 } // namespace dbus 123 } // namespace phosphor 124