Add median* condition
The median condition determines the median value from a configured group
of properties and checks that against a defined condition to determine
whether or not the callback is called.
*Note: When 2 properties are used with the median condition, the
property determined to be the max is used instead of the average of the
2 properties. This is for providing a "worst case" median value.
An example would be to create a group consisting of multiple ambient
sensors where the median value from these ambient sensors would be used
to shutdown the system if above a given temperature.
i.e.)
- name: median temps
description: >
'If this condition passes the median ambient temperature
is too high(>= 45C). Shut the system down.'
class: condition
condition: median
paths: ambient sensors
properties: ambient temp
callback: ambient log and shutdown
op: '>='
bound: 45000
oneshot: true
Tested:
A defined median condition is generated according to the
MedianCondition class
The MedianCondition class produces a single median value from a
group of property values
Median value used against the given operation to determine if
callback is called or not
Change-Id: Icd53e1a6e30a263b7706a935f040eea97dcc2414
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/src/Makefile.am b/src/Makefile.am
index 3838e05..bac980e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -37,6 +37,7 @@
templates/callbackgroup.mako.cpp \
templates/conditional.mako.cpp \
templates/count.mako.cpp \
+ templates/median.mako.cpp \
templates/generated.mako.hpp \
templates/journal.mako.cpp \
templates/elog.mako.cpp \
diff --git a/src/median.hpp b/src/median.hpp
new file mode 100644
index 0000000..a3ffe67
--- /dev/null
+++ b/src/median.hpp
@@ -0,0 +1,123 @@
+#pragma once
+
+#include "callback.hpp"
+#include "data_types.hpp"
+
+#include <algorithm>
+#include <functional>
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+/** @class MedianCondition
+ * @brief Determine a median from properties and apply a condition.
+ *
+ * When invoked, a median class instance performs its condition
+ * test against a median value that has been determined from a set
+ * of configured properties.
+ *
+ * Once the median value is determined, a C++ relational operator
+ * is applied to it and a value provided by the configuration file,
+ * which determines if the condition passes or not.
+ *
+ * Where no property values configured are found to determine a median from,
+ * the condition defaults to `true` and passes.
+ *
+ * If the oneshot parameter is true, then this condition won't pass
+ * again until it fails at least once.
+ */
+template <typename T>
+class MedianCondition : public IndexedConditional
+{
+ public:
+ MedianCondition() = delete;
+ MedianCondition(const MedianCondition&) = default;
+ MedianCondition(MedianCondition&&) = default;
+ MedianCondition& operator=(const MedianCondition&) = default;
+ MedianCondition& operator=(MedianCondition&&) = default;
+ ~MedianCondition() = default;
+
+ MedianCondition(const PropertyIndex& conditionIndex,
+ const std::function<bool(T)>& _medianOp,
+ bool oneshot = false) :
+ IndexedConditional(conditionIndex),
+ medianOp(_medianOp), oneshot(oneshot)
+ {
+ }
+
+ bool operator()() override
+ {
+ // Default the condition result to true
+ // if no property values are found to produce a median.
+ auto result = true;
+ std::vector<T> values;
+ for (const auto& item : index)
+ {
+ const auto& storage = std::get<storageIndex>(item.second);
+ // Don't count properties that don't exist.
+ if (std::get<valueIndex>(storage.get()).empty())
+ {
+ continue;
+ }
+ values.emplace_back(
+ any_ns::any_cast<T>(std::get<valueIndex>(storage.get())));
+ }
+
+ if (!values.empty())
+ {
+ auto median = values.front();
+ // Get the determined median value
+ if (values.size() == 2)
+ {
+ // For 2 values, use the highest instead of the average
+ // for a worst case median value
+ median = *std::max_element(values.begin(), values.end());
+ }
+ else if (values.size() > 2)
+ {
+ const auto oddIt = values.begin() + values.size() / 2;
+ std::nth_element(values.begin(), oddIt, values.end());
+ median = *oddIt;
+ // Determine median for even number of values
+ if (index.size() % 2 == 0)
+ {
+ // Use average of middle 2 values for median
+ const auto evenIt = values.begin() + values.size() / 2 - 1;
+ std::nth_element(values.begin(), evenIt, values.end());
+ median = (median + *evenIt) / 2;
+ }
+ }
+
+ // Now apply the condition to the median value.
+ result = medianOp(median);
+ }
+
+ // If this was a oneshot and the the condition has already
+ // passed, then don't let it pass again until the condition
+ // has gone back to false.
+ if (oneshot && result && lastResult)
+ {
+ return false;
+ }
+
+ lastResult = result;
+ return result;
+ }
+
+ private:
+ /** @brief The comparison to perform on the median value. */
+ std::function<bool(T)> medianOp;
+ /** @brief If the condition can be allowed to pass again
+ on subsequent checks that are also true. */
+ const bool oneshot;
+ /** @brief The result of the previous check. */
+ bool lastResult = false;
+};
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/pdmgen.py b/src/pdmgen.py
index c4184fc..963c7b3 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -768,6 +768,33 @@
indent=indent)
+class MedianCondition(Condition, Renderer):
+ '''Handle the median condition config file directive.'''
+
+ def __init__(self, *a, **kw):
+ self.op = kw.pop('op')
+ self.bound = kw.pop('bound')
+ self.oneshot = TrivialArgument(
+ type='boolean',
+ value=kw.pop('oneshot', False))
+ super(MedianCondition, self).__init__(**kw)
+
+ def setup(self, objs):
+ '''Resolve type.'''
+
+ super(MedianCondition, self).setup(objs)
+ self.bound = TrivialArgument(
+ type=self.type,
+ value=self.bound)
+
+ def construct(self, loader, indent):
+ return self.render(
+ loader,
+ 'median.mako.cpp',
+ c=self,
+ indent=indent)
+
+
class Journal(Callback, Renderer):
'''Handle the journal callback config file directive.'''
@@ -1110,6 +1137,7 @@
},
'condition': {
'count': CountCondition,
+ 'median': MedianCondition,
},
}
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index d0a5ead..3436b49 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -6,6 +6,7 @@
#include <chrono>
#include <string>
#include "count.hpp"
+#include "median.hpp"
#include "data_types.hpp"
#include "journal.hpp"
#include "elog.hpp"
diff --git a/src/templates/median.mako.cpp b/src/templates/median.mako.cpp
new file mode 100644
index 0000000..7730483
--- /dev/null
+++ b/src/templates/median.mako.cpp
@@ -0,0 +1,4 @@
+std::make_unique<MedianCondition<${c.datatype}>>(
+${indent(1)}ConfigPropertyIndicies::get()[${c.instances}],
+${indent(1)}[](const auto& item){return item ${c.op} ${c.bound.argument(loader, indent=indent +1)};},
+${indent(1)}${c.oneshot.argument(loader, indent=indent +1)})\