xref: /openbmc/phosphor-dbus-monitor/src/median.hpp (revision 34ef1e526b8eedf5ce9a0396b2a2627218a39378)
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