blob: 99769721a30c6443a9878dae57b3d5df3f7e47c8 [file] [log] [blame]
Matthew Barthefdd03c2019-09-04 15:44:35 -05001#pragma once
2
3#include "callback.hpp"
4#include "data_types.hpp"
5
6#include <algorithm>
7#include <functional>
8
9namespace phosphor
10{
11namespace dbus
12{
13namespace 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 */
33template <typename T>
34class 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)
George Liu3fe976c2022-06-21 09:37:04 +080049 {}
Matthew Barthefdd03c2019-09-04 15:44:35 -050050
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.
Patrick Williams26dc0bc2022-06-16 17:06:18 -050061 if (!std::get<valueIndex>(storage.get()).has_value())
Matthew Barthefdd03c2019-09-04 15:44:35 -050062 {
63 continue;
64 }
65 values.emplace_back(
Patrick Williams26dc0bc2022-06-16 17:06:18 -050066 std::any_cast<T>(std::get<valueIndex>(storage.get())));
Matthew Barthefdd03c2019-09-04 15:44:35 -050067 }
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