Notice to GitKraken users: A vulnerability has been found in the SSH key generation of GitKraken versions 7.6.0 to 8.0.0 (https://www.gitkraken.com/blog/weak-ssh-key-fix). If you use GitKraken and have generated a SSH key using one of these versions, please remove it both from your local workstation and from your LRZ GitLab profile.

21.10.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

Commit ed0d3654 authored by Alessio Netti's avatar Alessio Netti
Browse files

Merge remote-tracking branch 'remotes/origin/development'

parents 7142e0f0 47da1a33
......@@ -220,7 +220,7 @@ void OperatorManager::unloadPlugin(const string& id) {
_plugins.clear();
}
bool OperatorManager::init(boost::asio::io_service& io, const string& plugin) {
bool OperatorManager::init(const string& plugin) {
if(_status != LOADED) {
LOG(error) << "Cannot init, OperatorManager is not loaded!";
return false;
......@@ -232,12 +232,12 @@ bool OperatorManager::init(boost::asio::io_service& io, const string& plugin) {
out = true;
LOG(info) << "Init " << p.id << " operator plugin";
for (const auto &op : p.configurator->getOperators())
op->init(io);
op->init(_io);
}
return out;
}
bool OperatorManager::reload(boost::asio::io_service& io, const string& plugin) {
bool OperatorManager::reload(const string& plugin) {
if(_status != LOADED) {
LOG(error) << "Cannot reload, OperatorManager is not loaded!";
return false;
......@@ -259,7 +259,7 @@ bool OperatorManager::reload(boost::asio::io_service& io, const string& plugin)
return false;
} else
for (const auto &op : p.configurator->getOperators())
op->init(io);
op->init(_io);
}
return out;
}
......
......@@ -82,8 +82,9 @@ public:
/**
* @brief Class constructor
* @param io Boost ASIO service to be used
*/
OperatorManager() { _status = CLEAR; }
OperatorManager(boost::asio::io_context& io) : _io(io) { _status = CLEAR; }
/**
* @brief Class destructor
......@@ -130,11 +131,10 @@ public:
* This method must be called after "load", and before "start". It will prepare operators for
* operation, and initialize the related sensors and caches.
*
* @param io Boost ASIO service to be used
* @param plugin Name of the plugin on which the action must be performed. If none, all plugins will be affected
* @return true if successful, false otherwise
*/
bool init(boost::asio::io_service& io, const string& plugin="");
bool init(const string& plugin="");
/**
* @brief Reload one or more plugins
......@@ -142,11 +142,10 @@ public:
* This method will cause all running operators of a plugin to be stopped and destroyed. The
* configuration file is then read once again, and new operators are created and initialized.
*
* @param io Boost ASIO service to be used
* @param plugin Name of the plugin on which the action must be performed. If none, all plugins will be affected
* @return true if successful, false otherwise
*/
bool reload(boost::asio::io_service& io, const string& plugin="");
bool reload(const string& plugin="");
/**
* @brief Start one or more stored plugins
......@@ -452,6 +451,7 @@ private:
*/
void PUT_analytics_operator(endpointArgs);
boost::asio::io_context& _io;
};
#endif //PROJECT_ANALYTICSMANAGER_H
......@@ -237,7 +237,7 @@ public:
virtual void init(boost::asio::io_service& io) final override {
OperatorInterface::init(io);
for(const auto u : _units)
for(const auto& u : _units)
u->init(_interval, _queueSize);
this->execOnInit();
......
......@@ -180,11 +180,11 @@ public:
* @param interval Sampling interval in milliseconds
*/
void init(unsigned int interval, unsigned int queueSize) override {
for(const auto s : _outputs)
for(const auto &s : _outputs)
if (!s->isInit())
s->initSensor(interval, queueSize);
for (const auto &su : _subUnits)
for (const auto s : su->getOutputs())
for (const auto &s : su->getOutputs())
if (!s->isInit())
s->initSensor(interval, queueSize);
}
......
......@@ -219,7 +219,7 @@ void CARestAPI::POST_write(endpointArgs) {
DCDB::SensorId sid;
if (sid.mqttTopicConvert(mqttTopic)) {
_sensorCache->storeSensor(sid, ts.getRaw(), value);
_sensorDataStore->insert(&sid, ts.getRaw(), value);
_sensorDataStore->insert(sid, ts.getRaw(), value);
_influxCounter++;
if (_influxSettings->publish && (_influxSensors.find(sid.getId()) == _influxSensors.end())) {
......@@ -267,7 +267,7 @@ void CARestAPI::PUT_analytics_reload(endpointArgs) {
// Wait until controller is paused in order to reload plugins
_analyticsController->halt(true);
if (!_analyticsController->getManager()->reload(_analyticsController->getIoService(), plugin)) {
if (!_analyticsController->getManager()->reload(plugin)) {
res.body() = "Plugin not found or reload failed, please check the config files and MQTT topics!\n";
res.result(http::status::not_found);
} else if (!_analyticsController->getManager()->start(plugin)) {
......@@ -297,7 +297,7 @@ void CARestAPI::PUT_analytics_load(endpointArgs) {
res.body() = "Operator plugin " + plugin + " successfully loaded!\n";
res.result(http::status::ok);
_analyticsController->getManager()->init(_analyticsController->getIoService(), plugin);
_analyticsController->getManager()->init(plugin);
} else {
res.body() = "Failed to load operator plugin " + plugin + "!\n";
res.result(http::status::internal_server_error);
......
......@@ -86,16 +86,6 @@ bool AnalyticsController::initialize(Configuration& settings) {
if(!_queryEngine.updating.is_lock_free())
LOG(warning) << "This machine does not support lock-free atomics. Performance may be degraded.";
LOG(info) << "Creating threads...";
// Dummy to keep io service alive even if no tasks remain (e.g. because all sensors have been stopped over REST API)
// Inherited from DCDB Pusher
_keepAliveWork = make_shared<boost::asio::io_service::work>(_io);
// Create pool of threads which handle the sensors
for(size_t i = 0; i < _settings.threads; i++) {
_threads.create_thread(bind(static_cast< size_t (boost::asio::io_service::*) () >(&boost::asio::io_service::run), &_io));
}
LOG(info) << "Threads created!";
_initialized = true;
return true;
}
......@@ -106,7 +96,7 @@ void AnalyticsController::run() {
return;
LOG(info) << "Init operators...";
_manager->init(_io);
_manager->init();
LOG(info) << "Starting operators...";
_manager->start();
LOG(info) << "Sensors started!";
......
......@@ -65,10 +65,10 @@ public:
* @param dcdbCfg SensorConfig object to be used to retrieve sensor meta-data from Cassandra
* @param dcdbStore SensorDataStore object to be used to insert sensor readings into Cassandra
*/
AnalyticsController(DCDB::SensorConfig *dcdbCfg, DCDB::SensorDataStore *dcdbStore) {
_dcdbCfg = dcdbCfg;
_dcdbStore = dcdbStore;
_manager = make_shared<OperatorManager>();
AnalyticsController(DCDB::SensorConfig *dcdbCfg, DCDB::SensorDataStore *dcdbStore, boost::asio::io_context& io)
: _dcdbCfg(dcdbCfg),
_dcdbStore(dcdbStore) {
_manager = make_shared<OperatorManager>(io);
_navigator = nullptr;
_sensorCache = nullptr;
_metadataStore = nullptr;
......@@ -179,13 +179,6 @@ public:
*/
uint64_t getReadingCtr() { uint64_t ctr=_readingCtr; _readingCtr=0; return ctr; }
/**
* @brief Return the io_service used by the analytics controller.
*
* @return Reference to this object's boost::asio::io_service.
*/
boost::asio::io_service& getIoService() { return _io; }
/**
* @brief Rebuilds the internal sensor navigator.
*
......@@ -227,8 +220,6 @@ private:
// Main management thread for the analytics controller
boost::thread _mainThread;
// IO service for the operators
boost::asio::io_service _io;
// Underlying thread pool
boost::thread_group _threads;
// Dummy task to keep thread pool alive
......
......@@ -54,6 +54,7 @@
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <dcdb/libconfig.h>
#include <dcdb/connection.h>
#include <dcdb/sensordatastore.h>
#include <dcdb/jobdatastore.h>
......@@ -92,6 +93,7 @@ uint64_t msgCtr;
uint64_t readingCtr;
uint64_t dbQueryCtr;
uint64_t cachedQueryCtr;
uint64_t missesQueryCtr;
SensorCache mySensorCache;
AnalyticsController* analyticsController;
DCDB::Connection* dcdbConn;
......@@ -103,6 +105,7 @@ MetadataStore *metadataStore;
CARestAPI* httpsServer = nullptr;
DCDB::SCError err;
QueryEngine& queryEngine = QueryEngine::getInstance();
boost::shared_ptr<boost::asio::io_context::work> keepAliveWork;
logger_t lg;
bool jobQueryCallback(const string& jobId, const uint64_t startTs, const uint64_t endTs, vector<qeJobData>& buffer, const bool rel, const bool range, const string& domainId) {
......@@ -202,6 +205,8 @@ bool sensorGroupQueryCallback(const std::vector<string>& names, const uint64_t s
reading.timestamp = r.timeStamp.getRaw();
buffer.push_back(reading);
}
} else {
missesQueryCtr += topics.size();
}
}
catch (const std::exception &e) {}
......@@ -271,6 +276,8 @@ void sigHandler(int sig)
LOG(fatal) << "Received SIGUSR1 via REST API";
retCode = !httpsServer ? EXIT_SUCCESS : httpsServer->getReturnCode();
}
keepAliveWork.reset();
keepRunning = 0;
}
......@@ -669,6 +676,9 @@ int main(int argc, char* const argv[]) {
exit(EXIT_FAILURE);
}
}
libConfig.init();
libConfig.setTempDir(pluginSettings.tempdir);
//set up logger to file
if (settings.logLevelFile >= 0) {
......@@ -754,8 +764,11 @@ int main(int argc, char* const argv[]) {
metadataStore->store(*sBuf.getPattern(), sBuf);
}
publicSensors.clear();
analyticsController = new AnalyticsController(mySensorConfig, mySensorDataStore);
boost::asio::io_context io;
boost::thread_group threads;
analyticsController = new AnalyticsController(mySensorConfig, mySensorDataStore, io);
analyticsController->setCache(&mySensorCache);
analyticsController->setMetadataStore(metadataStore);
queryEngine.setFilter(analyticsSettings.filter);
......@@ -779,10 +792,12 @@ int main(int argc, char* const argv[]) {
LOG(info) << " MQTT-listenAddress: " << settings.mqttListenHost << ":" << settings.mqttListenPort;
LOG(info) << " CacheInterval: " << int(pluginSettings.cacheInterval/1000) << " [s]";
LOG(info) << " CleaningInterval: " << settings.cleaningInterval << " [s]";
LOG(info) << " Threads: " << settings.threads;
LOG(info) << " MessageThreads: " << settings.messageThreads;
LOG(info) << " MessageSlots: " << settings.messageSlots;
LOG(info) << " Daemonize: " << (settings.daemonize ? "Enabled" : "Disabled");
LOG(info) << " StatisticsInterval: " << settings.statisticsInterval << " [s]";
LOG(info) << " StatisticsMqttPart: " << settings.statisticsMqttPart;
LOG(info) << " MQTT-prefix: " << pluginSettings.mqttPrefix;
LOG(info) << " Auto-publish: " << (pluginSettings.autoPublish ? "Enabled" : "Disabled");
LOG(info) << " Write-Dir: " << pluginSettings.tempdir;
......@@ -852,9 +867,20 @@ int main(int argc, char* const argv[]) {
if (settings.validateConfig)
return EXIT_SUCCESS;
else
analyticsController->start();
LOG(info) << "Creating threads...";
// Dummy to keep io service alive even if no tasks remain (e.g. because all sensors have been stopped over REST API)
// Inherited from DCDB Pusher
keepAliveWork = boost::make_shared<boost::asio::io_context::work>(io);
// Create pool of threads which handle the sensors
for(size_t i = 0; i < settings.threads; i++) {
threads.create_thread(bind(static_cast< size_t (boost::asio::io_context::*) () >(&boost::asio::io_context::run), &io));
}
LOG(info) << "Threads created!";
analyticsController->start();
LOG(info) << "AnalyticsController running...";
/*
* Start the MQTT Message Server.
*/
......@@ -869,7 +895,7 @@ int main(int argc, char* const argv[]) {
* Start the HTTP Server for the REST API
*/
if (restAPISettings.enabled) {
httpsServer = new CARestAPI(restAPISettings, &config.influxSettings, &mySensorCache, mySensorDataStore, mySensorConfig, analyticsController, &ms, analyticsController->getIoService());
httpsServer = new CARestAPI(restAPISettings, &config.influxSettings, &mySensorCache, mySensorDataStore, mySensorConfig, analyticsController, &ms, io);
config.readRestAPIUsers(httpsServer);
httpsServer->start();
LOG(info) << "HTTP Server running...";
......@@ -886,6 +912,7 @@ int main(int argc, char* const argv[]) {
readingCtr = 0;
dbQueryCtr = 0;
cachedQueryCtr = 0;
missesQueryCtr = 0;
start = getTimestamp();
uint64_t lastCleanup = start;
......@@ -893,31 +920,32 @@ int main(int argc, char* const argv[]) {
LOG(info) << "Collect Agent running...";
while(keepRunning) {
start = getTimestamp();
if(NS_TO_S(start) - NS_TO_S(lastCleanup) > settings.cleaningInterval) {
uint64_t purged = mySensorCache.clean(S_TO_NS(settings.cleaningInterval));
lastCleanup = start;
if(purged > 0)
LOG(info) << "Cache: purged " << purged << " obsolete entries";
}
if(newAutoPub) {
newAutoPub = false;
mySensorConfig->setPublishedSensorsWritetime(getTimestamp());
}
start = getTimestamp();
if(NS_TO_S(start) - NS_TO_S(lastCleanup) > settings.cleaningInterval) {
uint64_t purged = mySensorCache.clean(S_TO_NS(settings.cleaningInterval));
lastCleanup = start;
if(purged > 0)
LOG(info) << "Cache: purged " << purged << " obsolete entries";
}
if(newAutoPub) {
newAutoPub = false;
mySensorConfig->setPublishedSensorsWritetime(getTimestamp());
}
sleep(sleepInterval);
if((settings.statisticsInterval > 0) && keepRunning) {
/* not really thread safe but will do the job */
end = getTimestamp();
elapsed = (float)(NS_TO_S(end) - NS_TO_S(start));
float aIns = ceil(((float)analyticsController->getReadingCtr()) / elapsed);
float cacheReq = ceil(((float)cachedQueryCtr) / elapsed);
float missesReq = ceil(((float)missesQueryCtr) / elapsed);
float dbReq = ceil(((float)dbQueryCtr) / elapsed);
float rIns = restAPISettings.enabled ? ceil(((float)httpsServer->getInfluxCounter()) / elapsed) : 0.0f;
float mIns = ceil(((float)readingCtr) / elapsed);
float mMsg = ceil(((float) msgCtr) / elapsed);
LOG(info) << "Performance: MQTT [" << std::fixed << std::setprecision(0) << mIns << " ins/s|" << mMsg << " msg/s] REST [" << rIns << " ins/s] Analytics [" << aIns << " ins/s] Cache [" << cacheReq << " req/s] DB [" << dbReq << " req/s]";
LOG(info) << "Performance: MQTT [" << std::fixed << std::setprecision(0) << mIns << " ins/s|" << mMsg << " msg/s] REST [" << rIns << " ins/s] Analytics [" << aIns << " ins/s] Cache [" << cacheReq << " req/s] DB [" << dbReq << " req/s] Miss [" << missesReq << " req/s]";
std::map<std::string, hostInfo_t> lastSeen = ms.collectLastSeen();
uint64_t connectedHosts = 0;
for (auto h: lastSeen) {
......@@ -926,8 +954,25 @@ int main(int argc, char* const argv[]) {
}
}
LOG(info) << "Connected hosts: " << connectedHosts;
if (settings.statisticsMqttPart.size() > 0) {
std::string statisticsMqttTopic = pluginSettings.mqttPrefix + settings.statisticsMqttPart;
std::list<SensorDataStoreReading> stats;
stats.push_back(SensorDataStoreReading(SensorId(statisticsMqttTopic+"/msgsRcvd"), end, msgCtr));
stats.push_back(SensorDataStoreReading(SensorId(statisticsMqttTopic+"/cachedQueries"), end, cachedQueryCtr));
stats.push_back(SensorDataStoreReading(SensorId(statisticsMqttTopic+"/missedQueries"), end, missesQueryCtr));
stats.push_back(SensorDataStoreReading(SensorId(statisticsMqttTopic+"/dbQueries"), end, dbQueryCtr));
stats.push_back(SensorDataStoreReading(SensorId(statisticsMqttTopic+"/readingsRcvd"), end, readingCtr));
stats.push_back(SensorDataStoreReading(SensorId(statisticsMqttTopic+"/hosts"), end, connectedHosts));
for (auto s: stats) {
mySensorDataStore->insert(s);
mySensorCache.storeSensor(s);
}
}
msgCtr = 0;
cachedQueryCtr = 0;
missesQueryCtr = 0;
dbQueryCtr = 0;
readingCtr = 0;
}
......
......@@ -61,6 +61,10 @@ void SensorCache::storeSensor(SensorId sid, uint64_t ts, int64_t val) {
}
}
void SensorCache::storeSensor(const SensorDataStoreReading& s) {
storeSensor(s.sensorId, s.timeStamp.getRaw(), s.value);
}
int64_t SensorCache::getSensor(SensorId sid, uint64_t avg) {
/* Remove the reserved bytes to leverage the standard find function */
sid.setRsvd(0);
......
......@@ -32,6 +32,7 @@
#include <atomic>
#include <dcdb/sensorid.h>
#include <dcdb/timestamp.h>
#include <dcdb/sensordatastore.h>
#include "cacheentry.h"
using namespace DCDB;
......@@ -63,6 +64,13 @@ public:
**/
void storeSensor(SensorId sid, uint64_t ts, int64_t val);
/**
* @brief Store a sensor reading in the SensorCache.
*
* @param s The SensorDataStoreReading object of the sensor data to be cached.
**/
void storeSensor(const SensorDataStoreReading& s);
/**
* @brief Return a sensor reading or the average of the last readings
* from the SensorCache.
......
......@@ -207,6 +207,7 @@ public:
bool validateConfig = false;
bool daemonize = false;
int statisticsInterval = 60;
std::string statisticsMqttPart;
uint64_t threads = DEFAULT_THREADS;
int logLevelFile = -1;
int logLevelCmd = DEFAULT_LOGLEVEL;
......
......@@ -59,7 +59,8 @@ public:
SCALE_SET = 128,
TTL_SET = 256,
INTERVAL_SET = 512,
OPERATIONS_SET = 1024
OPERATIONS_SET = 1024,
DELTA_SET = 2048
} MetadataMask;
SensorMetadata() :
......@@ -74,6 +75,7 @@ public:
ttl(0),
interval(0),
operations(),
delta(false),
setMask(0) {}
SensorMetadata(const SensorMetadata& other) {
......@@ -90,6 +92,7 @@ public:
ttl = other.ttl;
interval = other.interval;
operations = other.operations;
delta = other.delta;
}
SensorMetadata& operator=(const SensorMetadata& other) {
......@@ -105,6 +108,7 @@ public:
ttl = other.ttl;
interval = other.interval;
operations = other.operations;
delta = other.delta;
return *this;
}
......@@ -170,6 +174,9 @@ public:
setOperations(_parseOperations(buf, ','));
oldPos = payload.length();
break;
case 11 :
setDelta(to_bool(buf));
break;
}
}
fieldCtr++;
......@@ -211,6 +218,8 @@ public:
setTTL(stoull(val.second.data()) * 1000000);
} else if (boost::iequals(val.first, "operations")) {
setOperations(val.second.data());
} else if (boost::iequals(val.first, "delta")) {
setDelta(to_bool(val.second.data()));
}
}
}
......@@ -252,6 +261,7 @@ public:
buf += (setMask & INTERVAL_SET) ? to_string(interval / 1000000) + "," : ",";
buf += (setMask & TTL_SET) ? to_string(ttl / 1000000) + "," : ",";
buf += (setMask & OPERATIONS_SET) ? _dumpOperations(',') + "," : ",";
buf += (setMask & DELTA_SET) ? bool_to_str(delta) + "," : ",";
return buf;
}
......@@ -281,7 +291,8 @@ public:
const uint64_t* getInterval() const { return (setMask & INTERVAL_SET) ? &interval : nullptr; }
const set<string>* getOperations() const { return (setMask & OPERATIONS_SET) ? &operations : nullptr; }
const string getOperationsString() const { return (setMask & OPERATIONS_SET) ? _dumpOperations() : ""; }
const bool* getDelta() const { return (setMask & DELTA_SET) ? &delta : nullptr; }
void setIsOperation(bool o) { isOperation = o; setMask = setMask | IS_OPERATION_SET; }
void setIsVirtual(bool v) { isVirtual = v; setMask = setMask | IS_VIRTUAL_SET; }
void setIntegrable(bool i) { integrable = i; setMask = setMask | INTEGRABLE_SET; }
......@@ -294,7 +305,8 @@ public:
void setInterval(uint64_t i) { interval = i; setMask = setMask | INTERVAL_SET; }
void setOperations(const string& o) { setOperations(_parseOperations(o)); }
void clearOperations() { operations.clear(); setMask = setMask & ~OPERATIONS_SET; }
void setDelta(bool d) { delta = d; setMask = setMask | DELTA_SET; }
// Merges a set of operations with the local one
void setOperations(const set<string>& o) {
if(setMask & OPERATIONS_SET)
......@@ -375,6 +387,9 @@ protected:
config.push_back(boost::property_tree::ptree::value_type("ttl", boost::property_tree::ptree(to_string(ttl / 1000000))));
if(setMask & OPERATIONS_SET)
config.push_back(boost::property_tree::ptree::value_type("operations", boost::property_tree::ptree(_dumpOperations())));
if(setMask & DELTA_SET)
config.push_back(boost::property_tree::ptree::value_type("delta", boost::property_tree::ptree(bool_to_str(delta))));
}
// Protected class members
......@@ -389,6 +404,7 @@ protected:
uint64_t ttl;
uint64_t interval;
set<string> operations;
bool delta;
uint64_t setMask;
};
......
......@@ -52,6 +52,7 @@ public:
_cacheInterval(900000),
_subsamplingFactor(1),
_subsamplingIndex(0),
_factor(1),
_cache(nullptr),
_delta(false),
_deltaMax(LLONG_MAX),
......@@ -79,6 +80,7 @@ public:
_cacheInterval(other._cacheInterval),
_subsamplingFactor(other._subsamplingFactor),
_subsamplingIndex(0),
_factor(other._factor),
_cache(nullptr),
_delta(other._delta),
_deltaMax(other._deltaMax),
......@@ -103,6 +105,7 @@ public:
_cacheInterval = other._cacheInterval;
_subsamplingFactor = other._subsamplingFactor;
_subsamplingIndex = 0;
_factor = other._factor;
_cache.reset(nullptr);
_delta = other._delta;
_deltaMax = other._deltaMax;
......@@ -128,9 +131,12 @@ public:
const std::string& getName() const { return _name; }
const std::string& getMqtt() const { return _mqtt; }
bool getSkipConstVal() const { return _skipConstVal; }
bool getPublish() const { return _publish; }
bool getPublish() const { return _publish; }
bool getDelta() const { return _delta; }
unsigned getCacheInterval() const { return _cacheInterval; }
int getSubsampling() const { return _subsamplingFactor; }
double getFactor() const { return _factor; }
const CacheEntry* const getCache() const { return _cache.get(); }
const reading_t& getLatestValue() const { return _latestValue; }
const bool isInit() const { return _cache && _readingQueue; }
......@@ -149,6 +155,7 @@ public:
void setMqtt(const std::string& mqtt) { _mqtt = mqtt; }
void setCacheInterval(unsigned cacheInterval) { _cacheInterval = cacheInterval; }
void setSubsampling(int factor) { _subsamplingFactor = factor; }
void setFactor(const double &factor) { _factor = factor; }
void setLastRaw(int64_t raw) { _lastRawValue.value = raw; }
void setLastURaw(uint64_t raw) { _lastRawUValue.value = raw; }
......@@ -185,9 +192,9 @@ public:
if( _delta ) {
if (!_firstReading) {
if (rawReading.value < _lastRawValue.value)
reading.value = (rawReading.value + ((int64_t)_deltaMax - _lastRawValue.value)) * factor;
reading.value = (rawReading.value + ((int64_t)_deltaMax - _lastRawValue.value)) * factor * _factor;
else
reading.value = (rawReading.value - _lastRawValue.value) * factor;
reading.value = (rawReading.value - _lastRawValue.value) * factor * _factor;
} else {
_firstReading = false;
_lastRawValue = rawReading;
......@@ -196,7 +203,7 @@ public:
_lastRawValue = rawReading;
}
else
reading.value = rawReading.value * factor;
reading.value = rawReading.value * factor * _factor;
storeReadingLocal(reading);
if (storeGlobal) {
......@@ -226,9 +233,9 @@ public:
if( _delta ) {
if (!_firstReading) {
if (rawReading.value < _lastRawUValue.value)
reading.value = (rawReading.value + (_deltaMax - _lastRawUValue.value)) * factor;
reading.value = (rawReading.value + (_deltaMax - _lastRawUValue.value)) * factor * _factor;
else
reading.value = (rawReading.value - _lastRawUValue.value) * factor;
reading.value = (rawReading.value - _lastRawUValue.value) * factor * _factor;
} else {
_firstReading = false;
_lastRawUValue = rawReading;
......@@ -237,7 +244,7 @@ public:
_lastRawUValue = rawReading;
}
else
reading.value = rawReading.value * factor;
reading.value = rawReading.value * factor * _factor;
storeReadingLocal(reading);
if (