diff options
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/callback.hpp | 79 | ||||
-rw-r--r-- | src/count.hpp | 82 | ||||
-rw-r--r-- | src/example/example.yaml | 27 | ||||
-rwxr-xr-x | src/pdmgen.py | 98 | ||||
-rw-r--r-- | src/templates/conditional.mako.cpp | 3 | ||||
-rw-r--r-- | src/templates/count.mako.cpp | 4 | ||||
-rw-r--r-- | src/templates/generated.mako.hpp | 17 |
8 files changed, 312 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index c77c0a8..4d6658d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,6 +23,8 @@ CLEANFILES = generated.hpp TEMPLATES = \ templates/callbackgroup.mako.cpp \ + templates/conditional.mako.cpp \ + templates/count.mako.cpp \ templates/generated.mako.hpp \ templates/journal.mako.cpp diff --git a/src/callback.hpp b/src/callback.hpp index 7c6f69b..f7886cb 100644 --- a/src/callback.hpp +++ b/src/callback.hpp @@ -28,6 +28,50 @@ class Callback virtual void operator()() = 0; }; +/** @class Conditional + * @brief Condition interface. + * + * Conditions of any type can be tested for true or false. + */ +class Conditional +{ + public: + Conditional() = default; + Conditional(const Conditional&) = delete; + Conditional(Conditional&&) = default; + Conditional& operator=(const Conditional&) = delete; + Conditional& operator=(Conditional&&) = default; + virtual ~Conditional() = default; + + /** @brief Test the condition. */ + virtual bool operator()() = 0; +}; + +/** @class IndexedConditional + * @brief Condition with an index. + */ +class IndexedConditional : public Conditional +{ + public: + IndexedConditional() = delete; + IndexedConditional(const IndexedConditional&) = delete; + IndexedConditional(IndexedConditional&&) = default; + IndexedConditional& operator=(const IndexedConditional&) = delete; + IndexedConditional& operator=(IndexedConditional&&) = default; + virtual ~IndexedConditional() = default; + + explicit IndexedConditional(const PropertyIndex& conditionIndex) + : Conditional(), index(conditionIndex) {} + + /** @brief Test the condition. */ + virtual bool operator()() override = 0; + + protected: + + /** @brief Property names and their associated storage. */ + const PropertyIndex& index; +}; + /** @class IndexedCallback * @brief Callback with an index. */ @@ -90,6 +134,41 @@ class GroupOfCallbacks : public Callback const std::vector<size_t>& graph; }; +/** @class ConditionalCallback + * @brief Callback adaptor that asssociates a condition with a callback. + */ +template <typename CallbackAccess> +class ConditionalCallback: public Callback +{ + public: + ConditionalCallback() = delete; + ConditionalCallback(const ConditionalCallback&) = delete; + ConditionalCallback(ConditionalCallback&&) = default; + ConditionalCallback& operator=(const ConditionalCallback&) = delete; + ConditionalCallback& operator=(ConditionalCallback&&) = default; + ~ConditionalCallback() = default; + ConditionalCallback( + const std::vector<size_t>& graphEntry, + Conditional& cond) + : graph(graphEntry), condition(cond) {} + + /** @brief Run the callback if the condition is satisfied. */ + void operator()() override + { + if (condition()) + { + (*CallbackAccess::get()[graph[0]])(); + } + } + + private: + /** @brief The index of the callback to conditionally invoke. */ + const std::vector<size_t>& graph; + + /** @brief The condition to test. */ + Conditional& condition; +}; + } // namespace monitoring } // namespace dbus } // namespace phosphor diff --git a/src/count.hpp b/src/count.hpp new file mode 100644 index 0000000..a588ccc --- /dev/null +++ b/src/count.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "callback.hpp" +#include "data_types.hpp" + +namespace phosphor +{ +namespace dbus +{ +namespace monitoring +{ + +/** @class CountCondition + * @brief Count properties that satisfy a condition. + * + * When invoked, a count class instance performs its condition + * test in two passes. + * + * In pass one, apply a C++ relational operator to the value of + * each property in the index and a value provided by the + * configuration file. + * + * Count the number of properties that pass the test in pass + * one. In pass two, apply a second C++ relational operator + * to the number of properties that pass the test from pass one + * to a count provided by the configuration file. + */ +template <typename T> +class CountCondition : public IndexedConditional +{ + public: + CountCondition() = delete; + CountCondition(const CountCondition&) = default; + CountCondition(CountCondition&&) = default; + CountCondition& operator=(const CountCondition&) = default; + CountCondition& operator=(CountCondition&&) = default; + ~CountCondition() = default; + + CountCondition( + const PropertyIndex& conditionIndex, + const std::function<bool(size_t)>& _countOp, + const std::function<bool(T)>& _propertyOp) : + IndexedConditional(conditionIndex), + countOp(_countOp), + propertyOp(_propertyOp) {} + + bool operator()() override + { + // Count the number of properties in the index that + // pass the condition specified in the config file. + auto count = std::count_if( + index.cbegin(), + index.cend(), + [this](const auto & item) + // *INDENT-OFF* + { + const auto& storage = std::get<2>( + item.second); + // Don't count properties that don't exist. + if (storage.get().empty()) + { + return false; + } + const auto& value = any_ns::any_cast<T>( + storage); + return propertyOp(value); + }); + // *INDENT-ON* + + // Now apply the count condition to the count. + return countOp(count); + } + + private: + /** @brief The comparison to perform on the count. */ + std::function<bool(size_t)> countOp; + /** @brief The comparison to perform on each property. */ + std::function<bool(T)> propertyOp; +}; +} // namespace monitoring +} // namespace dbus +} // namespace phosphor diff --git a/src/example/example.yaml b/src/example/example.yaml index 0f286dc..65f6537 100644 --- a/src/example/example.yaml +++ b/src/example/example.yaml @@ -93,3 +93,30 @@ callback: group members: - example journal callback + +- name: example count condition + description: > + 'Conditions or conditional callbacks apply a test prior to invoking + the callback function. + + All conditional callbacks must specify the callback to issue if + the condition evaulates. + + The count condition applies the op comparison operator to the value of each + property in the specified groups. It then counts the number of properties + that pass the comparison, and applies another comparison on the result + against the specified bound. + + For example, a callback that requires at least three temperature sensors + in the group to be higher than 115 degrees might use a count condition + with an op of >, a count op of >=, a bound of 115, and a countbound of 3.' + + class: condition + condition: count + paths: example path group + properties: example property group + callback: example callback group + countop: '>=' + countbound: 3 + op: '>=' + bound: 115 diff --git a/src/pdmgen.py b/src/pdmgen.py index b20436e..4a958e1 100755 --- a/src/pdmgen.py +++ b/src/pdmgen.py @@ -420,6 +420,99 @@ class Callback(HasPropertyIndex): super(Callback, self).__init__(**kw) +class ConditionCallback(ConfigEntry, Renderer): + '''Handle the journal callback config file directive.''' + + def __init__(self, *a, **kw): + self.condition = kw.pop('condition') + self.instance = kw.pop('instance') + super(ConditionCallback, self).__init__(**kw) + + def factory(self, objs): + '''Create a graph instance for this callback.''' + + args = { + 'configfile': self.configfile, + 'members': [self.instance], + 'class': 'callbackgroup', + 'callbackgroup': 'callback', + 'name': [self.instance] + } + + entry = CallbackGraphEntry(**args) + add_unique(entry, objs, config=self.configfile) + + super(ConditionCallback, self).factory(objs) + + def setup(self, objs): + '''Resolve condition and graph entry.''' + + self.graph = get_index( + objs, + 'callbackgroup', + [self.instance], + config=self.configfile) + + self.condition = get_index( + objs, + 'condition', + self.name, + config=self.configfile) + + super(ConditionCallback, self).setup(objs) + + def construct(self, loader, indent): + return self.render( + loader, + 'conditional.mako.cpp', + c=self, + indent=indent) + + +class Condition(HasPropertyIndex): + '''Interface and common logic for conditions.''' + + def __init__(self, *a, **kw): + self.callback = kw.pop('callback') + super(Condition, self).__init__(**kw) + + def factory(self, objs): + '''Create a callback instance for this conditional.''' + + args = { + 'configfile': self.configfile, + 'condition': self.name, + 'class': 'callback', + 'callback': 'conditional', + 'instance': self.callback, + 'name': self.name, + } + + callback = ConditionCallback(**args) + add_unique(callback, objs, config=self.configfile) + callback.factory(objs) + + super(Condition, self).factory(objs) + + +class CountCondition(Condition, Renderer): + '''Handle the count condition config file directive.''' + + def __init__(self, *a, **kw): + self.countop = kw.pop('countop') + self.countbound = kw.pop('countbound') + self.op = kw.pop('op') + self.bound = kw.pop('bound') + super(CountCondition, self).__init__(**kw) + + def construct(self, loader, indent): + return self.render( + loader, + 'count.mako.cpp', + c=self, + indent=indent) + + class Journal(Callback, Renderer): '''Handle the journal callback config file directive.''' @@ -526,6 +619,9 @@ class Everything(Renderer): 'journal': Journal, 'group': GroupOfCallbacks, }, + 'condition': { + 'count': CountCondition, + }, } if cls not in class_map: @@ -617,6 +713,7 @@ class Everything(Renderer): self.watches = kw.pop('watch', []) self.callbacks = kw.pop('callback', []) self.callbackgroups = kw.pop('callbackgroup', []) + self.conditions = kw.pop('condition', []) super(Everything, self).__init__(**kw) @@ -640,6 +737,7 @@ class Everything(Renderer): instancegroups=self.instancegroups, callbacks=self.callbacks, callbackgroups=self.callbackgroups, + conditions=self.conditions, indent=Indent())) if __name__ == '__main__': diff --git a/src/templates/conditional.mako.cpp b/src/templates/conditional.mako.cpp new file mode 100644 index 0000000..3cc9564 --- /dev/null +++ b/src/templates/conditional.mako.cpp @@ -0,0 +1,3 @@ +std::make_unique<ConditionalCallback<ConfigPropertyCallbacks>>( +${indent(1)}ConfigPropertyCallbackGroups::get()[${c.graph}], +${indent(1)}*ConfigConditions::get()[${c.condition}])\ diff --git a/src/templates/count.mako.cpp b/src/templates/count.mako.cpp new file mode 100644 index 0000000..d348518 --- /dev/null +++ b/src/templates/count.mako.cpp @@ -0,0 +1,4 @@ +std::make_unique<CountCondition<${c.datatype}>>( +${indent(1)}ConfigPropertyIndicies::get()[${c.instances}], +${indent(1)}[](const auto& item){return item ${c.countop} ${c.countbound};}, +${indent(1)}[](const auto& item){return item ${c.op} ${c.bound};})\ diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp index 67b9149..a4819e2 100644 --- a/src/templates/generated.mako.hpp +++ b/src/templates/generated.mako.hpp @@ -4,6 +4,7 @@ #include <array> #include <string> +#include "count.hpp" #include "data_types.hpp" #include "journal.hpp" #include "propertywatchimpl.hpp" @@ -145,6 +146,22 @@ struct ConfigPropertyCallbackGroups } }; +struct ConfigConditions +{ + using Conditions = std::array<std::unique_ptr<Conditional>, ${len(conditions)}>; + + static auto& get() + { + static const Conditions propertyConditions = + { +% for c in conditions: + ${c.construct(loader, indent=indent +3)}, +% endfor + }; + return propertyConditions; + } +}; + struct ConfigPropertyCallbacks { using Callbacks = std::array<std::unique_ptr<Callback>, ${len(callbacks)}>; |