ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
StatusAggregator.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#include "StatusAggregator.h"
4
5#include <ChimeraTK/SystemTags.h>
6
7#include <list>
8#include <regex>
9#include <utility>
10
11namespace ChimeraTK {
12
13 /********************************************************************************************************************/
14
15 StatusAggregator::StatusAggregator(ModuleGroup* owner, const std::string& name, const std::string& description,
16 PriorityMode mode, std::unordered_set<std::string> tagsToAggregate,
17 const std::unordered_set<std::string>& outputTags, std::string warnMixedMessage)
18 : ApplicationModule(owner, ".", description, outputTags), _output(this, name), _mode(mode),
19 _tagsToAggregate(std::move(tagsToAggregate)), _warnMixedMessage(std::move(warnMixedMessage)) {
20 // Check that size of tagsToAggregate is 1.
21 // There is a design decision pending whether multiple tags should be logical AND or logical OR (#13256).
22 if(_tagsToAggregate.size() > 1) {
23 throw ChimeraTK::logic_error(
24 "StatusAggregator: List of tagsToAggregate is currently limited to one tag (see #13256).");
25 }
26 // add reserved tag tagAggregatedStatus to the status output, so it can be detected by other StatusAggregators
28 // search the variable tree for StatusOutputs and create the matching inputs
30 }
31
32 /********************************************************************************************************************/
33
35 auto model = dynamic_cast<ModuleGroup*>(_owner)->getModel();
36
37 // set of potential inputs for this StatusAggregator instance
38 std::set<std::string> inputPathsSet;
39 // set of inputs for other, already created, StatusAggregator instances
40 std::set<std::string> anotherStatusAgregatorInputSet;
41 // map which assigns fully qualified path of StatusAggregator output to the ully qualified path StatusAggregator output message
42 std::map<std::string, std::string> statusToMessagePathsMap;
43
44 auto scanModel = [&](auto proxy) {
45 if constexpr(ChimeraTK::Model::isApplicationModule(proxy)) {
46 auto* staAggPtr = dynamic_cast<StatusAggregator*>(&proxy.getApplicationModule());
47 if(staAggPtr != nullptr) {
48 if(staAggPtr == this) {
49 return;
50 }
51 // ATTENTION. This loop is implementing a logical AND.
52 // There is a design decision pending whether this is the wanted behaviour (#13256).
53 // The constructor is currently limiting to one tag, so existing logic will not break.
54 for(const auto& tagToAgregate : _tagsToAggregate) {
55 // Each tag attached to this StatusAggregator must be present at all StatusOutputs to be aggregated
56 auto outputNode = VariableNetworkNode(staAggPtr->_output._status);
57 if(outputNode.getTags().find(tagToAgregate) == outputNode.getTags().end()) {
58 return;
59 }
60 }
61
62 inputPathsSet.insert(staAggPtr->_output._status.getModel().getFullyQualifiedPath());
63
64 statusToMessagePathsMap[staAggPtr->_output._status.getModel().getFullyQualifiedPath()] =
65 staAggPtr->_output._message.getModel().getFullyQualifiedPath();
66
67 for(auto& anotherStatusAgregatorInput : staAggPtr->_inputs) {
68 anotherStatusAgregatorInputSet.insert(
69 anotherStatusAgregatorInput._status.getModel().getFullyQualifiedPath());
70 }
71 }
72 }
73
74 if constexpr(ChimeraTK::Model::isVariable(proxy)) {
75 // check whether its not output of this (current) StatusAggregator. 'Current' StatusAggregator output is also
76 // visible in the scanned model and should be ignored
77 if(proxy.getFullyQualifiedPath() == _output._status.getModel().getFullyQualifiedPath()) {
78 return;
79 }
80
81 auto tags = proxy.getTags();
82
83 // find another aggregator output - this is already covered by checking if given module is a StatusAggregator
84 if(tags.find(StatusAggregator::tagAggregatedStatus) != tags.end()) {
85 return;
86 }
87
88 // find status output - this is potential candidate to be aggregated
89 if(tags.find(ChimeraTK::SystemTags::statusOutput) != tags.end()) {
90 for(const auto& tagToAgregate : _tagsToAggregate) {
91 // ATTENTION. This loop is implementing a logical AND.
92 // There is a design decision pending whether this is the wanted behaviour (#13256).
93 // The constructor is currently limiting to one tag, so existing logic will not break.
94 //
95 // Each tag attached to this StatusAggregator must be present at all StatusOutputs to be aggregated
96 if(tags.find(tagToAgregate) == tags.end()) {
97 return;
98 }
99 }
100 inputPathsSet.insert(proxy.getFullyQualifiedPath());
101
102 // check for presence of message
103 std::string fqn = proxy.getFullyQualifiedPath();
104 if(tags.find(StatusWithMessage::tagStatusHasMessage) != tags.end()) {
105 statusToMessagePathsMap[proxy.getFullyQualifiedPath()] = fqn + "_message";
106 }
107 }
108 }
109 };
110
111 model.visit(scanModel, ChimeraTK::Model::keepApplicationModules || ChimeraTK::Model::keepProcessVariables,
112 ChimeraTK::Model::breadthFirstSearch, ChimeraTK::Model::keepOwnership);
113
114 for(const auto& pathToBeRemoved : anotherStatusAgregatorInputSet) {
115 inputPathsSet.erase(pathToBeRemoved);
116 }
117
118 for(const auto& pathToBeAggregated : inputPathsSet) {
119 _inputs.emplace_back(
120 this, pathToBeAggregated, pathToBeAggregated, std::unordered_set<std::string>{tagInternalVars});
121 if(!statusToMessagePathsMap[pathToBeAggregated].empty()) {
122 _inputs.back().setMessageSource(statusToMessagePathsMap[pathToBeAggregated]);
123 }
124 }
125 }
126
127 int StatusAggregator::getPriority(StatusOutput::Status status) const {
128 using Status = StatusOutput::Status;
129
130 // static helps against initializing over and over again
131 static const std::map<PriorityMode, std::map<Status, int32_t>> map_priorities{
132 {PriorityMode::fwko, {{Status::OK, 1}, {Status::FAULT, 3}, {Status::OFF, 0}, {Status::WARNING, 2}}},
133 {PriorityMode::fwok, {{Status::OK, 0}, {Status::FAULT, 3}, {Status::OFF, 1}, {Status::WARNING, 2}}},
134 {PriorityMode::ofwk, {{Status::OK, 0}, {Status::FAULT, 2}, {Status::OFF, 3}, {Status::WARNING, 1}}},
135 {PriorityMode::fw_warn_mixed, {{Status::OK, -1}, {Status::FAULT, 3}, {Status::OFF, -1}, {Status::WARNING, 2}}}};
136
137 return map_priorities.at(_mode).at(status);
138 }
139
140 /********************************************************************************************************************/
141
143 // set up inputsMap which gives StatusWithMessageInput for transferelementId of members
144 std::map<TransferElementID, StatusWithMessageInput*> inputsMap;
145 for(auto& x : _inputs) {
146 inputsMap[x._status.getId()] = &x;
147 if(x.hasMessageSource) {
148 inputsMap[x._message.getId()] = &x;
149 }
150 }
151
152 auto rag = readAnyGroup();
153 while(true) {
154 // find highest priority status of all inputs
155 StatusOutput::Status status{StatusOutput::Status::FAULT}; // initialised just to prevent warning
156
157 // store the input at which the highest-priority status was found ("selected input"), so we can access the
158 // corresponding message
159 StatusWithMessageInput* statusOrigin = nullptr;
160
161 // cache priority of the current "selected" status input
162 int statusPrio = 0; // initialised just to prevent warning
163
164 // flag whether status has been set from an input already
165 bool statusSet = false;
166
167 for(auto& inputPair : _inputs) {
168 StatusPushInput& input = inputPair._status;
169 auto prio = getPriority(input);
170
171 // Select the input if:
172 // - no input has been selected so far, or
173 // - the priority of the value is higher than the previously selected one, or
174 // - the priority is the same but the input has a lower version number than the previously selected one.
175 if(!statusSet || prio > statusPrio ||
176 (input == status && statusOrigin != nullptr &&
177 input.getVersionNumber() < statusOrigin->_status.getVersionNumber())) {
178 status = input;
179 statusOrigin = &inputPair;
180 statusPrio = prio;
181 statusSet = true;
182 }
183 else if(prio == -1) { // -1 means, we need to warn about mixed values
184 if(statusPrio == -1 && input != status) {
185 status = StatusOutput::Status::WARNING;
186 statusOrigin = nullptr;
187 statusPrio = getPriority(status);
188 }
189 }
190 }
191
192 // some input must be selected due to the logic
193 assert(statusSet);
194
195 // write status output with message from selected input. The output is written only if the output's value has
196 // changed (writeIfDifferent).
197 if(!statusOrigin) {
198 // this can only happen if warning about mixed values
199 assert(status == StatusOutput::Status::WARNING);
201 }
202 else {
203 if(status != StatusOutput::Status::OK) {
204 // this either copies the message from corresponding string variable, or sets a generic message
205 auto msg = statusOrigin->getMessage();
206 _output.writeIfDifferent(status, msg);
207 }
208 else {
210 }
211 }
212
213 // wait for changed inputs
214 waitForChange:
215 auto change = rag.readAny();
216 auto f = inputsMap.find(change);
217 if(f != inputsMap.end()) {
218 auto* varPair = f->second;
219 if(!varPair->update(change)) {
220 goto waitForChange; // inputs not in consistent state yet
221 }
222 }
223
224 // handle request for debug info
225 if(change == _debug.getId()) {
226 auto myLog = logger(Logger::Severity::info);
227 myLog << "StatusAggregtor (";
228 switch(_mode) {
230 myLog << "fwok";
231 break;
233 myLog << "fwko";
234 break;
236 myLog << "fw_warn_mixed";
237 break;
239 myLog << "ofwk";
240 break;
241 };
242 myLog << ") " << static_cast<VariableNetworkNode>(_output._status).getQualifiedName()
243 << " debug info:" << std::endl;
244 for(auto& inputPair : _inputs) {
245 StatusPushInput& input = inputPair._status;
246 myLog << static_cast<VariableNetworkNode>(input).getQualifiedName() << " = " << input;
247 if(inputPair.hasMessageSource) {
248 myLog << " ; " << static_cast<VariableNetworkNode>(inputPair._message).getQualifiedName() << " = "
249 << std::string(inputPair._message);
250 }
251 myLog << std::endl;
252 }
253 myLog << "output status is " << _output._status << " (" << std::string(_output._message) << ")" << std::endl;
254
255 myLog << "debug info finished." << std::endl;
256 goto waitForChange;
257 }
258 }
259 }
260
261 /********************************************************************************************************************/
262
264 return DataValidity::ok;
265 }
266
267 /********************************************************************************************************************/
268
269 void StatusAggregator::setWarnMixedMessage(std::string message) {
270 _warnMixedMessage = std::move(message);
271 }
272
273 /********************************************************************************************************************/
274
275} // namespace ChimeraTK
Logger::StreamProxy logger(Logger::Severity severity)
Convenicene function to obtain a logger stream with the given Severity.
ChimeraTK::Model::ApplicationModuleProxy getModel()
Return the application model proxy representing this module.
void addTag(const std::string &tag)
Add a tag.
Model::ProcessVariableProxy getModel() const
std::string getFullyQualifiedPath() const
Return the fully qualified path.
Definition Model.cc:25
std::string getQualifiedName() const override
Get the fully qualified name of the module instance, i.e.
Definition Module.cc:219
EntityOwner * _owner
Owner of this instance.
Definition Module.h:145
ChimeraTK::ReadAnyGroup readAnyGroup()
Create a ChimeraTK::ReadAnyGroup for all readable variables in this Module.
Definition Module.cc:54
Class describing a node of a variable network.
constexpr bool isApplicationModule(const PROPERTY_OR_PROXY &)
Definition Model.h:637
constexpr bool isVariable(const PROPERTY_OR_PROXY &)
Definition Model.h:661
InvalidityTracer application module.
The StatusAggregator collects results of multiple StatusMonitor instances and aggregates them into a ...
VoidInput _debug
Allow runtime debugging.
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
PriorityMode
Possible status priority modes used during aggregation of unequal Status values.
@ fw_warn_mixed
fault - warning - ok or off, mixed state of ok or off results in warning
@ ofwk
off - fault - warning - ok
@ fwko
fault - warning - ok - off
@ fwok
fault - warning - off - ok
void populateStatusInput()
Recursivly search for StatusMonitors and other StatusAggregators.
void setWarnMixedMessage(std::string message)
Set a custom message for the warn mixed state.
std::vector< StatusWithMessageInput > _inputs
All status inputs to be aggregated.
int getPriority(StatusOutput::Status status) const
Convert Status value into a priority (high integer value = high priority), depending on chosen Priori...
PriorityMode _mode
Priority mode used in aggregation.
StatusWithMessage _output
The aggregated status output.
DataValidity getDataValidity() const override
Return the data validity flag.
std::string _warnMixedMessage
Error message for the warn_mixed condition.
static constexpr auto tagInternalVars
Reserved tag which is used to mark internal variables which should not be visible in the virtual hier...
std::unordered_set< std::string > _tagsToAggregate
List of tags to aggregate.
static constexpr auto tagAggregatedStatus
Reserved tag which is used to mark aggregated status outputs (need to stop searching further down the...
Special StatusPushInput which reads from a StatusOutput and also handles the type conversion.
static constexpr auto tagStatusHasMessage
Reserved tag which is used to mark presense of the message output.
void writeIfDifferent(StatusOutput::Status status, std::string message)
ScalarOutput< std::string > _message
This is for consistent readout of StatusWithMessage - ApplicationCore version.