UnitGenerator.h 12.7 KB
Newer Older
Alessio Netti's avatar
Alessio Netti committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//
// Created by Netti, Alessio on 21.01.19.
//

#ifndef PROJECT_UNITGENERATOR_H
#define PROJECT_UNITGENERATOR_H

#include <set>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include "../SensorNavigator.h"
#include "UnitTemplate.h"

using namespace std;

typedef enum inputMode_t { SELECTIVE = 1, ALL = 2, ALL_RECURSIVE = 3 } inputMode_t;

/**
 * Helper template to generate Analyzer Units
 *
 * This template decouples the unit-related configuration logic from AnalyerConfiguratorTemplate, so that users can
 * override such template without losing access to the unit system.
 *
 */
template <class SBase=SensorBase>
class UnitGenerator {
    // The template shall only be instantiated for classes which derive from SensorBase
    static_assert(std::is_base_of<SensorBase, SBase>::value, "SBase must derive from SensorBase!");

public:

    /**
    * @brief            Class constructor
    */
    UnitGenerator() {}

    /**
    * @brief            Class constructor
    *
    * @param navi       SensorNavigator object to be used internally
    */
    UnitGenerator(shared_ptr<SensorNavigator> navi) {
        _navi = navi;
    }

    /**
    * @brief            Class destructor
    */
    ~UnitGenerator() {}

    /**
    * @brief            Sets the internal SensorNavigator object
    *
    *                   Note that a SensorNavigator must be set before the UnitGenerator can be used
    *
    * @param navi       Shared pointer to a SensorNavigator object
    */
    void setNavigator(shared_ptr<SensorNavigator> navi) { _navi = navi; }

    /**
    * @brief            Parses a string encoding a tree level
    *
    *                   This method serves to parse strings that are used to express hierarchy levels in config files
    *                   of the data analytics framework. These strings are in the format "<unit-X>.*", and signify
    *                   "sensors that are in nodes X levels up from the deepest level in the sensor tree". As such,
    *                   the method returns the depth level of sensors represented by the input string. Note that
    *                   "<unit+X>.*" is not supported, because the system relates to the deepest level of the current
    *                   sensor tree.
    *
    * @param s          String to be parsed
    * @return           Absolute depth level in the sensor tree that is encoded in the string
    */
    int  parseNodeLevelString(const string& s) {
        if(!_navi || !_navi->treeExists())
            throw runtime_error("UnitGenerator: SensorNavigator tree not initialized!");

        int _treeDepth = _navi->getTreeDepth();
        if(boost::regex_search(s.c_str(), _match, _blockRx)) {
            string blockMatch = _match.str(0);
            if(!boost::regex_search(blockMatch.c_str(), _match, _nodeRx))
                throw runtime_error("UnitGenerator: Syntax error in configuration!");
            blockMatch = _match.str(0);
            int lv = !boost::regex_search(blockMatch.c_str(), _match, _numRx) ? _treeDepth : _treeDepth - (int)stoi(_match.str(0));
            return lv<-1 ? -1 : lv;
        }
        else
            return -1;
    }

    /**
    * @brief            Resolves a string encoding a tree level starting from a given node
    *
    *                   This method takes as input strings in the format specified for parseNodeLevelString(). It then
    *                   takes as input also the name of a node in the sensor tree. The method will then return the set
    *                   of sensors expressed by "s", that belong to nodes encoded in its hierarchy level and that are
    *                   related to "node", either as ancestors or descendants. If a filter was included in the unit
    *                   clause, e.g. <unit - 1, filter cpu>freq, then only sensors in nodes matching the filter
    *                   regular expression will be returned.
    *
    * @param s          String to be parsed
    * @param node       Name of the target node
    * @param replace    If False only the names of the resolved units will be returned, without the sensor name
    * @return           Set of sensors encoded in "s" that are associated with "node"
    */
    set<string> *resolveNodeLevelString(const string& s, const string& node, const bool replace=true) {
        int level = parseNodeLevelString(s);
        set<string> *sensors = new set<string>();
        if( level <= -1 )
            sensors->insert(s);
        else {
            //Ensuring that only the "filter" clause matches is enough, since we already checked for the entire
            // < > configuration block in parseNodeLevelString
            boost::regex filter = boost::regex(boost::trim_copy(!boost::regex_search(s.c_str(), _match, _filterRx) ? ".*" : _match.str(0)));
            set<string> *nodes = _navi->navigate(node, level - _navi->getNodeDepth(node));
            // Filtering is performed here, as node names always include all upper levels of the hierarchy
            for(const auto& n : *nodes) {
                if (boost::regex_search(n.c_str(), _match, filter))
                    sensors->insert(replace ? boost::regex_replace(s, _blockRx, n) : n);
            }
            delete nodes;
        }
        return sensors;
    }

Alessio Netti's avatar
Alessio Netti committed
125
    //TODO: make all and all-recursive add sensors to existing ones
Alessio Netti's avatar
Alessio Netti committed
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
    /**
    * @brief                   Computes and instantiates units associated to the input analyzer
    *
    *                          This will compute the list of units that must be instantiated, starting from the
    *                          outputs of the analyzer. The inputs for each unit are then computed, and the units
    *                          finalized. The method takes as input two vectors of "prototype" input and output sensors
    *                          that will be used as "templates" to generate the units. Such prototype sensors must
    *                          be in the type of the template, and must have names containing the <unit> construct,
    *                          which will be use to resolve the actual units.
    *
    * @param inputs	           The vector of "prototype" sensor objects for inputs
    * @param outputs           The vector of "prototype" sensor objects for outputs
    * @param inputMode         Defines the method with which input sensors are instantiated for each unit
    * @param mqttPrefix        MQTT prefix to use for output sensors if only the "root" unit is defined
    * @return	               A vector of shared pointers to the generated unit objects
    */
    vector<shared_ptr<UnitTemplate<SBase>>> *generateUnits(vector<SBase>& inputs, vector<SBase>& outputs, inputMode_t inputMode, string mqttPrefix="") {
        // If no outputs are defined, no units can be instantiated
        if((inputs.size()==0 && inputMode==SELECTIVE) || outputs.size() == 0)
            throw invalid_argument("UnitGenerator: Invalid inputs or outputs!");

        // We iterate over the outputs, and compute their depth level in the current sensor tree. From such depth level,
        // the list of units is defined, consisting in all nodes in the sensor tree at the level of the outputs
        int unitLevel = parseNodeLevelString(outputs[0].getName());
        for(const auto& out : outputs)
            if(unitLevel != parseNodeLevelString(out.getName()))
                throw invalid_argument("UnitGenerator: Incoherent output levels!");

        set<string>* units = NULL;
        if(unitLevel > -1)
            units = resolveNodeLevelString(outputs[0].getName(), _navi->rootKey, false);
        // If no depth level was found (output sensor names do not contain any <unit-X> keyword) we assume that
        // everything relates to root
        else if(unitLevel == -1) {
            units = new set<string>();
            units->insert(_navi->rootKey);
        }

        if(!units || units->empty())
            throw invalid_argument("UnitGenerator: Invalid output level or unit specification!");


        // We iterate over the units, and resolve their inputs and outputs starting from the prototype definitions
        vector<shared_ptr<SBase>> unitInputs, unitOutputs;
        vector<shared_ptr<UnitTemplate<SBase>>> *unitObjects = new vector<shared_ptr<UnitTemplate<SBase>>>();
        set<string>* sensors;
        for(const auto& u : *units) {
            unitInputs.clear();
            unitOutputs.clear();
            if(inputMode == SELECTIVE)
                // Mapping inputs
                for(const auto& in : inputs) {
                    // Depending on the relationship of an input prototype sensor to the output level, it could be
                    // mapped to one sensor or more: for example, if output has level <unit-1>, and an input sensor
                    // has level <unit-2>, than the input will be unique, and the sensor associated to the father of the
                    // unit. If the other way around, the input will consist of multiple sensors, one for each child of
                    // the unit
                    sensors = resolveNodeLevelString(in.getName(), u);
                    if( sensors->empty() ) {
                        delete units;
                        delete sensors;
                        delete unitObjects;
                        throw invalid_argument("UnitGenerator: String " + in.getName() + " cannot be resolved!");
                    } else
                        for(const auto& s : *sensors) {
                            SBase uIn(in);
                            uIn.setName(s);
                            if (!_navi->sensorExists(uIn.getName())) {
                                delete units;
                                delete sensors;
                                delete unitObjects;
                                throw invalid_argument("UnitGenerator: Sensor " + in.getName() + " does not exist!");
                            }
                            unitInputs.push_back(make_shared<SBase>(uIn));
                        }
                    delete sensors;
                }
                // If no input sensors were specified, we pick all sensors related to the specific unit
                // This means that when output unit is "root" (or not specified) we get only sensors at the highest level
            else {
                sensors = _navi->getSensors(u, inputMode == ALL_RECURSIVE);
                for(const auto& s : *sensors) {
                    SBase uIn(s);
                    unitInputs.push_back(make_shared<SBase>(uIn));
                }
                delete sensors;
            }

            // Mapping outputs
            for(const auto& out : outputs) {
                SBase uOut(out);
                sensors = resolveNodeLevelString(uOut.getName(), u);
                uOut.setName(*sensors->begin());
                delete sensors;
                // If we are instantiating output sensors by unit, we generate mqtt topics by using the prefix
                // associated to the respective node in the sensor tree, and the sensor suffix itself
                if(u != _navi->rootKey) {
                    uOut.setMqtt(_navi->buildTopicForNode(u, uOut.getMqtt()));
                    // Duplicating the file sink adding the name of each unit to the path
                    string sPath = uOut.getSinkPath();
                    if(sPath != "") {
                        size_t idx = sPath.find_last_of("/\\");
                        if( idx != string::npos)
                            uOut.setSinkPath(sPath.substr(0, idx+1) + u + "_" + sPath.substr(idx+1, string::npos));
                        else
                            uOut.setSinkPath(u + "_" + sPath);
                    }
                }
                // If we are not using units (only unit is root, out of the hierarchy) we build sensors like in samplers
                else
                    uOut.setMqtt(mqttPrefix + uOut.getMqtt());
                unitOutputs.push_back(make_shared<SBase>(uOut));
            }
            shared_ptr<UnitTemplate<SBase>> unPtr = make_shared<UnitTemplate<SBase>>(u, unitInputs, unitOutputs);
            unitObjects->push_back(unPtr);

        }
        delete units;
        return unitObjects;
    }

protected:
    //Internal SensorNavigator object
    shared_ptr<SensorNavigator> _navi;

    //Regular expressions used in the parseNodeLevelString and resolveNodeLevelString methods
    boost::cmatch          _match;
    //Regex that matches the entire unit configuration block
    const boost::regex     _blockRx       = boost::regex("<.*>");
    const boost::regex     _nodeRx        = boost::regex("(?<=[,<])[ \\t]*unit[ \\t]*(-[ \\t]*[0-9]+[ \\t]*)?(?=[,>])");
    const boost::regex     _filterRx      = boost::regex("(?<=filter)[ \\t]+[^ \\t,>]+");
    const boost::regex     _numRx         = boost::regex("[0-9]+");

};

#endif //PROJECT_UNITGENERATOR_H