blob: a3ffe6735fff411e49a8c236240e63ce54632fcd [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)
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