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 
MedianCondition(const PropertyIndex & conditionIndex,const std::function<bool (T)> & _medianOp,bool oneshot=false)44     MedianCondition(const PropertyIndex& conditionIndex,
45                     const std::function<bool(T)>& _medianOp,
46                     bool oneshot = false) :
47         IndexedConditional(conditionIndex), medianOp(_medianOp),
48         oneshot(oneshot)
49     {}
50 
operator ()()51     bool operator()() override
52     {
53         // Default the condition result to true
54         // if no property values are found to produce a median.
55         auto result = true;
56         std::vector<T> values;
57         for (const auto& item : index)
58         {
59             const auto& storage = std::get<storageIndex>(item.second);
60             // Don't count properties that don't exist.
61             if (!std::get<valueIndex>(storage.get()).has_value())
62             {
63                 continue;
64             }
65             values.emplace_back(
66                 std::any_cast<T>(std::get<valueIndex>(storage.get())));
67         }
68 
69         if (!values.empty())
70         {
71             auto median = values.front();
72             // Get the determined median value
73             if (values.size() == 2)
74             {
75                 // For 2 values, use the highest instead of the average
76                 // for a worst case median value
77                 median = *std::max_element(values.begin(), values.end());
78             }
79             else if (values.size() > 2)
80             {
81                 const auto oddIt = values.begin() + values.size() / 2;
82                 std::nth_element(values.begin(), oddIt, values.end());
83                 median = *oddIt;
84                 // Determine median for even number of values
85                 if (index.size() % 2 == 0)
86                 {
87                     // Use average of middle 2 values for median
88                     const auto evenIt = values.begin() + values.size() / 2 - 1;
89                     std::nth_element(values.begin(), evenIt, values.end());
90                     median = (median + *evenIt) / 2;
91                 }
92             }
93 
94             // Now apply the condition to the median value.
95             result = medianOp(median);
96         }
97 
98         // If this was a oneshot and the the condition has already
99         // passed, then don't let it pass again until the condition
100         // has gone back to false.
101         if (oneshot && result && lastResult)
102         {
103             return false;
104         }
105 
106         lastResult = result;
107         return result;
108     }
109 
110   private:
111     /** @brief The comparison to perform on the median value. */
112     std::function<bool(T)> medianOp;
113     /** @brief If the condition can be allowed to pass again
114                on subsequent checks that are also true. */
115     const bool oneshot;
116     /** @brief The result of the previous check. */
117     bool lastResult = false;
118 };
119 
120 } // namespace monitoring
121 } // namespace dbus
122 } // namespace phosphor
123