ChimeraTK-ApplicationCore 04.07.01
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 <utility>
8
9namespace ChimeraTK {
10
11 /********************************************************************************************************************/
12
13 StatusAggregator::StatusAggregator(ModuleGroup* owner, const std::string& name, const std::string& description,
14 PriorityMode mode, std::unordered_set<std::string> tagsToAggregate,
15 const std::unordered_set<std::string>& outputTags, std::string warnMixedMessage)
16 : ApplicationModule(owner, ".", description, outputTags), _output(this, name), _mode(mode),
17 _tagsToAggregate(std::move(tagsToAggregate)), _warnMixedMessage(std::move(warnMixedMessage)) {
18 // Check that size of tagsToAggregate is 1.
19 // There is a design decision pending whether multiple tags should be logical AND or logical OR (#13256).
20 if(_tagsToAggregate.size() > 1) {
21 throw ChimeraTK::logic_error(
22 "StatusAggregator: List of tagsToAggregate is currently limited to one tag (see #13256).");
23 }
24 // add reserved tag tagAggregatedStatus to the status output, so it can be detected by other StatusAggregators
26 // switch off propagation of DataValidity from the inputs, because of special handling (output is valid if one
27 // of the valid inputs has the highest-priority value).
29 // search the variable tree for StatusOutputs and create the matching inputs
31 }
32
33 /********************************************************************************************************************/
34
36 auto model = dynamic_cast<ModuleGroup*>(_owner)->getModel();
37
38 // set of potential inputs for this StatusAggregator instance
39 std::set<std::string> inputPathsSet;
40 // set of inputs for other, already created, StatusAggregator instances
41 std::set<std::string> anotherStatusAgregatorInputSet;
42 // map which assigns fully qualified path of StatusAggregator output to the ully qualified path StatusAggregator output message
43 std::map<std::string, std::string> statusToMessagePathsMap;
44
45 // define visitor function as a lambda, executed for each PV and StatusAggregator in the model
46 auto scanModel = [&](auto proxy) {
47 // Define helper lambda (yes, inside another lambda, sorry...) for checking if a PV meets a tag requirement.
48 // This avoids code duplication.
49 auto checkTag = [&](const auto& nodeOrProxy) -> bool {
50 // ATTENTION. This code is implementing a logical AND.
51 // The constructor is currently limiting to one tag. According to #13256 the logic should be configurable
52 // whether a logical AND or a logical OR is used, or only a single tag is allowed (which will be the default).
53 return std::ranges::all_of(_tagsToAggregate, [&](const auto& tagToAgregate) {
54 if(tagToAgregate.starts_with('!')) {
55 // tag is negated: tag must not be present to consider status for aggregation
56 return nodeOrProxy.getTags().find(negateTag(tagToAgregate)) == nodeOrProxy.getTags().end();
57 }
58 // tag is not negated: tag needs to be present to consider status for aggregation
59 return nodeOrProxy.getTags().find(tagToAgregate) != nodeOrProxy.getTags().end();
60 });
61 };
62
63 // First case: ApplicationModule, check if this is another StatusAggregator
64 if constexpr(ChimeraTK::Model::isApplicationModule(proxy)) {
65 auto* staAggPtr = dynamic_cast<StatusAggregator*>(&proxy.getApplicationModule());
66 if(staAggPtr != nullptr) {
67 if(staAggPtr == this) {
68 return;
69 }
70
71 // check if found status output matches our tag filter
72 if(!checkTag(VariableNetworkNode(staAggPtr->_output._status))) {
73 return;
74 }
75
76 inputPathsSet.insert(staAggPtr->_output._status.getModel().getFullyQualifiedPath());
77
78 statusToMessagePathsMap[staAggPtr->_output._status.getModel().getFullyQualifiedPath()] =
80
81 for(auto& anotherStatusAgregatorInput : staAggPtr->_inputs) {
82 anotherStatusAgregatorInputSet.insert(
83 anotherStatusAgregatorInput._status.getModel().getFullyQualifiedPath());
84 }
85 }
86 }
87
88 // Second case: ProcessVariable: use StatusOutputs not belonging to another StatusAggregator (if tag matches)
89 if constexpr(ChimeraTK::Model::isVariable(proxy)) {
90 // check whether its not output of this (current) StatusAggregator. 'Current' StatusAggregator output is also
91 // visible in the scanned model and should be ignored
92 if(proxy.getFullyQualifiedPath() == _output._status.getModel().getFullyQualifiedPath()) {
93 return;
94 }
95
96 auto tags = proxy.getTags();
97
98 // find another aggregator output - this is already covered by checking if given module is a StatusAggregator
99 if(tags.find(StatusAggregator::tagAggregatedStatus) != tags.end()) {
100 return;
101 }
102
103 // find status output - this is potential candidate to be aggregated
104 if(tags.find(ChimeraTK::SystemTags::statusOutput) != tags.end()) {
105 // check if found status output matches our tag filter
106 if(!checkTag(proxy)) {
107 return;
108 }
109
110 inputPathsSet.insert(proxy.getFullyQualifiedPath());
111
112 // check for presence of message
113 std::string fqn = proxy.getFullyQualifiedPath();
114 if(tags.find(StatusWithMessage::tagStatusHasMessage) != tags.end()) {
115 statusToMessagePathsMap[proxy.getFullyQualifiedPath()] = fqn + "_message";
116 }
117 }
118 }
119 };
120
121 model.visit(scanModel, ChimeraTK::Model::keepApplicationModules || ChimeraTK::Model::keepProcessVariables,
122 ChimeraTK::Model::breadthFirstSearch, ChimeraTK::Model::keepOwnership);
123
124 for(const auto& pathToBeRemoved : anotherStatusAgregatorInputSet) {
125 inputPathsSet.erase(pathToBeRemoved);
126 }
127
128 for(const auto& pathToBeAggregated : inputPathsSet) {
129 _inputs.emplace_back(
130 this, pathToBeAggregated, pathToBeAggregated, std::unordered_set<std::string>{tagInternalVars});
131 if(!statusToMessagePathsMap[pathToBeAggregated].empty()) {
132 _inputs.back().setMessageSource(statusToMessagePathsMap[pathToBeAggregated]);
133 }
134 }
135 }
136
137 /********************************************************************************************************************/
138
139 int StatusAggregator::getPriority(StatusOutput::Status status) const {
140 using Status = StatusOutput::Status;
141
142 // Attention! priority_table must follow the order of PriorityMode and Status values!
143 static_assert(int(PriorityMode::fwok) == 0);
144 static_assert(int(PriorityMode::fwko) == 1);
145 static_assert(int(PriorityMode::fw_warn_mixed) == 2);
146 static_assert(int(PriorityMode::ofwk) == 3);
147
148 static_assert(int(Status::OK) == 0);
149 static_assert(int(Status::FAULT) == 1);
150 static_assert(int(Status::OFF) == 2);
151 static_assert(int(Status::WARNING) == 3);
152
153 constexpr auto priority_table = std::array{
154 // PriorityMode::fwok
155 std::array<int32_t, 4>{
156 /* OK */ 0,
157 /* FAULT */ 3,
158 /* OFF */ 1,
159 /* WARNING */ 2,
160 },
161 // PriorityMode::fwko
162 std::array<int32_t, 4>{
163 /* OK */ 1,
164 /* FAULT */ 3,
165 /* OFF */ 0,
166 /* WARNING */ 2,
167 },
168 // PriorityMode::fw_warn_mixed
169 std::array<int32_t, 4>{
170 /* OK */ -1,
171 /* FAULT */ 3,
172 /* OFF */ -1,
173 /* WARNING */ 2,
174 },
175 // PriorityMode::ofwk
176 std::array<int32_t, 4>{
177 /* OK */ 0,
178 /* FAULT */ 2,
179 /* OFF */ 3,
180 /* WARNING */ 1,
181 },
182 };
183
184 return priority_table[int(_mode)][int(status)];
185
186 /*
187 For reference, we keep the old code here with an std::map. This creates issues since the static instance may go
188 away too early in the shutdown phase, resulting in a use-after-free. Once we have C++26 fully supported, we should
189 be able to use a constexpr std::map.
190
191 static const std::map<PriorityMode, std::map<Status, int32_t>> map_priorities{
192 {PriorityMode::fwok, {{Status::OK, 0}, {Status::FAULT, 3}, {Status::OFF, 1}, {Status::WARNING, 2}}},
193 {PriorityMode::fwko, {{Status::OK, 1}, {Status::FAULT, 3}, {Status::OFF, 0}, {Status::WARNING, 2}}},
194 {PriorityMode::fw_warn_mixed, {{Status::OK, -1}, {Status::FAULT, 3}, {Status::OFF, -1}, {Status::WARNING, 2}}},
195 {PriorityMode::ofwk, {{Status::OK, 0}, {Status::FAULT, 2}, {Status::OFF, 3}, {Status::WARNING, 1}}},
196 };
197 return map_priorities.at(_mode).at(status);
198 */
199 }
200
201 /********************************************************************************************************************/
202
204 // set up inputsMap which gives StatusWithMessageInput for transferelementId of members
205 std::map<TransferElementID, StatusWithMessageInput*> inputsMap;
206 for(auto& x : _inputs) {
207 inputsMap[x._status.getId()] = &x;
208 if(x.hasMessageSource) {
209 inputsMap[x._message.getId()] = &x;
210 }
211 }
212
213 auto rag = readAnyGroup();
214 while(true) {
215 // find highest priority status of all inputs
216 StatusOutput::Status status{StatusOutput::Status::FAULT}; // initialised just to prevent warning
217
218 // store the input at which the highest-priority status was found ("selected input"), so we can access the
219 // corresponding message
220 StatusWithMessageInput* statusOrigin = nullptr;
221
222 // cache priority of the current "selected" status input
223 int statusPrio = 0; // initialised just to prevent warning
224
225 // flag whether status has been set from an input already
226 bool statusSet = false;
227
228 // custom DataValidity propagation
229 bool hasInvalidInput{false};
230 bool forceValid{false};
231
232 for(auto& inputPair : _inputs) {
233 StatusPushInput& input = inputPair._status;
234 auto prio = getPriority(input);
235
236 // custom DataValidity propagation
237 if(input.dataValidity() == DataValidity::faulty) {
238 hasInvalidInput = true;
239 }
240 else if(prio == 3) {
241 // valid and highest priority (e.g. Status::FAULT except for PriorityMode::ofwk where it's Status::OFF)
242 forceValid = true;
243 }
244
245 // Select the input if:
246 // - no input has been selected so far, or
247 // - the priority of the value is higher than the previously selected one, or
248 // - the priority is the same but the input has a lower version number than the previously selected one.
249 if(!statusSet || prio > statusPrio ||
250 (input == status && statusOrigin != nullptr &&
251 input.getVersionNumber() < statusOrigin->_status.getVersionNumber())) {
252 status = input;
253 statusOrigin = &inputPair;
254 statusPrio = prio;
255 statusSet = true;
256 }
257 else if(prio == -1) { // -1 means, we need to warn about mixed values
258 if(statusPrio == -1 && input != status) {
259 status = StatusOutput::Status::WARNING;
260 statusOrigin = nullptr;
261 statusPrio = getPriority(status);
262 }
263 }
264 }
265
266 // some input must be selected due to the logic
267 assert(statusSet);
268
269 // custom DataValidity propagation
270 auto validity = (!hasInvalidInput || forceValid) ? DataValidity::ok : DataValidity::faulty;
271
272 _output._status.setDataValidity(validity);
273 _output._message.setDataValidity(validity);
274
275 // write status output with message from selected input. The output is written only if the output's value has
276 // changed (writeIfDifferent).
277 if(!statusOrigin) {
278 // this can only happen if warning about mixed values
279 assert(status == StatusOutput::Status::WARNING);
281 }
282 else {
283 if(status != StatusOutput::Status::OK) {
284 // this either copies the message from corresponding string variable, or sets a generic message
285 auto msg = statusOrigin->getMessage();
286 _output.writeIfDifferent(status, msg);
287 }
288 else {
290 }
291 }
292
293 // wait for changed inputs
294 waitForChange:
295 auto change = rag.readAny();
296 auto f = inputsMap.find(change);
297 if(f != inputsMap.end()) {
298 auto* varPair = f->second;
299 if(!varPair->update(change)) {
300 goto waitForChange; // inputs not in consistent state yet
301 }
302 }
303
304 // handle request for debug info
305 if(change == _debug.getId()) {
306 auto myLog = logger(Logger::Severity::info);
307 myLog << "StatusAggregtor (";
308 switch(_mode) {
310 myLog << "fwok";
311 break;
313 myLog << "fwko";
314 break;
316 myLog << "fw_warn_mixed";
317 break;
319 myLog << "ofwk";
320 break;
321 };
322 myLog << ") " << static_cast<VariableNetworkNode>(_output._status).getQualifiedName()
323 << " debug info:" << std::endl;
324 for(auto& inputPair : _inputs) {
325 StatusPushInput& input = inputPair._status;
326 myLog << static_cast<VariableNetworkNode>(input).getQualifiedName() << " = " << input;
327 if(inputPair.hasMessageSource) {
328 myLog << " ; " << static_cast<VariableNetworkNode>(inputPair._message).getQualifiedName() << " = "
329 << std::string(inputPair._message);
330 }
331 myLog << std::endl;
332 }
333 myLog << "output status is " << _output._status << " (" << std::string(_output._message) << ")" << std::endl;
334
335 myLog << "debug info finished." << std::endl;
336 goto waitForChange;
337 }
338 }
339 }
340
341 /********************************************************************************************************************/
342
344 return DataValidity::ok;
345 }
346
347 /********************************************************************************************************************/
348
349 void StatusAggregator::setWarnMixedMessage(std::string message) {
350 _warnMixedMessage = std::move(message);
351 }
352
353 /********************************************************************************************************************/
354
355} // 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 to all Application-type nodes inside this group.
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
Special StatusPushInput which reads from a StatusOutput and also handles the type conversion.
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.
constexpr auto explicitDataValidityTag
Special tag to designate that a node should not automatically take over DataValidity of its owning mo...
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...
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.