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 44ca1b2e authored by Daniele Tafani's avatar Daniele Tafani
Browse files

Stable version of Grafana Server (Works with Grafana 6.2.2)

parent 52eaf253
include ../config.mk
CXXFLAGS = -O2 -g --std=c++11 -Wall -Wno-unused-local-typedefs -Wno-deprecated-declarations -Wno-unknown-warning-option -fmessage-length=0 -I../include/ -I../lib/include -I$(DCDBDEPLOYPATH)/include -DBOOST_DATE_TIME_POSIX_TIME_STD_CONFIG -I$(DCDBDEPSPATH)/cpp-netlib-0.12.0-final/deps/asio/asio/include -I$(DCDBDEPSPATH)/cpp-netlib-0.12.0-final -I$(DCDBDEPSPATH)/cpp-netlib-0.12.0-final/deps/cxxopts/src -DASIO_HEADER_ONLY -DBOOST_TEST_DYN_LINK
OBJS = collectagent.o \
sensorcache.o \
simplemqttserver.o \
simplemqttserverthread.o \
simplemqttservermessage.o
LIBS = -L$(DCDBDEPLOYPATH)/lib/ -L../lib -ldcdb -pthread -lcassandra -luv -lboost_system -lboost_random -lboost_thread -lboost_date_time -lboost_regex -lcppnetlib-server-parsers -lcppnetlib-uri
CXXFLAGS = -O2 -g --std=c++11 -Wall -Wno-unused-function -Wno-unused-local-typedefs -Wno-deprecated-declarations -Wno-unknown-warning-option -fmessage-length=0 -I../common/include/ -I../lib/include -I$(DCDBDEPLOYPATH)/include -DBOOST_DATE_TIME_POSIX_TIME_STD_CONFIG -DBOOST_LOG_DYN_LINK -I$(DCDBDEPSPATH)/cpp-netlib-0.12.0-final/deps/asio/asio/include -DVERSION=\"$(VERSION)\"
OBJS = ../common/src/logging.o \
sensorcache.o \
collectagent.o \
configuration.o \
simplemqttserver.o \
simplemqttserverthread.o \
simplemqttservermessage.o
LIBS = -L$(DCDBDEPLOYPATH)/lib/ -L../lib -ldcdb -pthread -lcassandra -luv -lboost_system -lboost_random -lboost_thread -lboost_date_time -lboost_log_setup -lboost_log -lboost_regex -lcppnetlib-server-parsers -lcppnetlib-uri
TARGET = collectagent
.PHONY : clean install
......@@ -25,5 +27,8 @@ clean:
rm -f $(TARGET)
rm -f $(OBJS)
install: $(TARGET)
install_conf: config/collectagent.conf
install -m 644 $^ $(DCDBDEPLOYPATH)/etc/
install: $(TARGET) install_conf
install $(TARGET) $(DCDBDEPLOYPATH)/bin/
......@@ -37,12 +37,15 @@
#include <dcdb/connection.h>
#include <dcdb/sensordatastore.h>
#include <dcdb/sensorconfig.h>
#include <dcdb/version.h>
#include "version.h"
#include "configuration.h"
#include "simplemqttserver.h"
#include "messaging.h"
#include "abrt.h"
#include "dcdbdaemon.h"
#include "sensorcache.h"
#define __STDC_FORMAT_MACROS
......@@ -50,24 +53,26 @@
using namespace std;
#define LISTENHOST "localhost"
#define LISTENPORT "1883"
#define CASSANDRAHOST "127.0.0.1"
#define CASSANDRAPORT "9042"
#define RESTAPIHOST "0.0.0.0"
#define RESTAPIPORT "8080"
#define TTL "0"
int keepRunning;
bool statistics;
uint64_t msgCtr;
uint64_t pmsgCtr;
uint64_t readingCtr;
SensorCache mySensorCache;
DCDB::Connection* dcdbConn;
DCDB::SensorDataStore *mySensorDataStore;
DCDB::SensorCache mySensorCache;
DCDB::SensorConfig *mySensorConfig;
DCDB::SCError err;
logger_t lg;
/* Normal termination (SIGINT, CTRL+C) */
void sigHandler(int sig)
{
boost::log::sources::severity_logger<boost::log::trivial::severity_level> lg;
if( sig == SIGINT )
LOG(fatal) << "Received SIGINT";
else if( sig == SIGTERM )
LOG(fatal) << "Received SIGTERM";
keepRunning = 0;
}
......@@ -92,7 +97,7 @@ struct httpHandler_t {
boost::network::uri::query_map(uri, queries);
int avg = atoi(queries.find("avg")->second.c_str());
uint64_t val = mySensorCache.getSensor(uri.path(), (uint64_t) avg);
int64_t val = mySensorCache.getSensor(uri.path(), (uint64_t)avg * 1000000000);
data << val << "\n";
//data << "Sid : " << sid.toString() << ", Value: " << val << "." << std::endl;
......@@ -130,65 +135,97 @@ int mqttCallback(SimpleMQTTMessage *msg)
if (msg->isPublish())
pmsgCtr++;
uint64_t len;
/*
* Decode the message and put into the database.
*/
if (msg->isPublish()) {
mqttPayload buf, *payload;
uint64_t len;
len = msg->getPayloadLength();
//In the 64 bit message case, the collect agent provides a timestamp
if (len == sizeof(uint64_t)) {
payload = &buf;
payload->value = *((uint64_t*)msg->getPayload());
payload->timestamp = Messaging::calculateTimestamp();
len = sizeof(uint64_t) * 2;
}
//...otherwise it just retrieves it from the MQTT message payload.
else if((len%sizeof(mqttPayload)==0) && (len>0)){
payload = (mqttPayload*)msg->getPayload();
}
//...otherwise this message is malformed -> ignore...
else {
cout << "Message malformed\n";
return 1;
}
const char *topic = msg->getTopic().c_str();
// We check whether the topic includes the \DCDB_MAP\ keyword, indicating that the payload will contain the
// sensor's name. In that case, we set the mappingMessage flag to true, and filter the keyword out of the prefix
// We use strncmp as it is the most efficient way to do it
if (strncmp(topic, DCDB_MAP, DCDB_MAP_LEN) == 0) {
if ((len = msg->getPayloadLength()) == 0) {
LOG(error) << "Empty topic-to-name mapping message received";
return 1;
}
/*
* Check if we can decode the message topic
* into a valid SensorId. If successful, store
* the record in the database.
*/
DCDB::SensorId sid;
if (sid.mqttTopicConvert(msg->getTopic())) {
#if 0
cout << "Topic decode successful:" << endl
<< " Raw: " << hex << setw(16) << setfill('0') << sid.getRaw()[0] << hex << setw(16) << setfill('0') << sid.getRaw()[1] << endl
<< " DeviceLocation: " << hex << setw(16) << setfill('0') << sid.getDeviceLocation() << endl
<< " device_id: " << hex << setw(8) << setfill('0') << sid.getDeviceSensorId().device_id << endl
<< " reserved: " << hex << setw(4) << setfill('0') << sid.getDeviceSensorId().rsvd << endl
<< " sensor_number: " << hex << setw(4) << setfill('0') << sid.getDeviceSensorId().sensor_number << endl << dec;
cout << "Payload (" << len/sizeof(mqttPayload) << " messages):"<< endl;
for (uint64_t i=0; i<len/sizeof(mqttPayload); i++) {
cout << " " << i << ": ts=" << payload[i].timestamp << " val=" << payload[i].value << endl;
string sensorName((char *) msg->getPayload(), len);
err = mySensorConfig->publishSensor(sensorName.c_str(), topic + DCDB_MAP_LEN);
// PublishSensor does most of the error checking for us
switch (err) {
case DCDB::SC_INVALIDPATTERN:
LOG(error) << "Invalid sensor topic : " << msg->getTopic();
return 1;
case DCDB::SC_INVALIDPUBLICNAME:
LOG(error) << "Invalid sensor public name: " << sensorName;
return 1;
case DCDB::SC_INVALIDSESSION:
LOG(error) << "Cannot reach sensor data store.";
return 1;
default:
break;
}
cout << endl;
} else {
mqttPayload buf, *payload;
len = msg->getPayloadLength();
//In the 64 bit message case, the collect agent provides a timestamp
if (len == sizeof(uint64_t)) {
payload = &buf;
payload->value = *((int64_t *) msg->getPayload());
payload->timestamp = Messaging::calculateTimestamp();
len = sizeof(uint64_t) * 2;
}
//...otherwise it just retrieves it from the MQTT message payload.
else if ((len % sizeof(mqttPayload) == 0) && (len > 0)) {
payload = (mqttPayload *) msg->getPayload();
}
//...otherwise this message is malformed -> ignore...
else {
LOG(error) << "Message malformed";
return 1;
}
/*
* Check if we can decode the message topic
* into a valid SensorId. If successful, store
* the record in the database.
*/
DCDB::SensorId sid;
if (sid.mqttTopicConvert(msg->getTopic())) {
#if 0
cout << "Topic decode successful:" << endl
<< " Raw: " << hex << setw(16) << setfill('0') << sid.getRaw()[0] << hex << setw(16) << setfill('0') << sid.getRaw()[1] << endl
<< " DeviceLocation: " << hex << setw(16) << setfill('0') << sid.getDeviceLocation() << endl
<< " device_id: " << hex << setw(8) << setfill('0') << sid.getDeviceSensorId().device_id << endl
<< " sensor_number: " << hex << setw(4) << setfill('0') << sid.getDeviceSensorId().sensor_number << endl << dec;
cout << "Payload (" << len/sizeof(mqttPayload) << " messages):"<< endl;
for (uint64_t i=0; i<len/sizeof(mqttPayload); i++) {
cout << " " << i << ": ts=" << payload[i].timestamp << " val=" << payload[i].value << endl;
}
cout << endl;
#endif
for (uint64_t i=0; i<len/sizeof(mqttPayload); i++) {
mySensorDataStore->insert(&sid, payload[i].timestamp, payload[i].value);
mySensorCache.storeSensor(sid, payload[i].timestamp, payload[i].value);
std::list<DCDB::SensorDataStoreReading> readings;
for (uint64_t i = 0; i < len / sizeof(mqttPayload); i++) {
DCDB::SensorDataStoreReading r(sid, payload[i].timestamp, payload[i].value);
readings.push_back(r);
mySensorCache.storeSensor(sid, payload[i].timestamp, payload[i].value);
}
mySensorDataStore->insertBatch(readings);
readingCtr+= readings.size();
//mySensorCache.dump();
}
//mySensorCache.dump();
}
#if 1
else {
cout << "Wrong topic format: " << msg->getTopic() << "\n";
return 1;
}
else {
cout << "Wrong topic format: " << msg->getTopic() << "\n";
return 1;
}
#endif
}
}
return 0;
}
......@@ -200,72 +237,98 @@ int mqttCallback(SimpleMQTTMessage *msg)
* Print usage information
*/
void usage() {
Configuration config("");
globalCA_t& defaults = config.getGlobal();
/*
1 2 3 4 5 6 7 8
012345678901234567890123456789012345678901234567890123456789012345678901234567890
*/
cout << "Usage:" << endl;
cout << " collectagent [-m<host>] [-r<host>] [-c<host>] [-u<username>] [-p<password>] [-t<ttl>] [-d] [-s]" << endl;
cout << " collectagent [-d] [-s] [-x] [-m<host>] [-r<host>] [-c<host>] [-u<username>] [-p<password>] [-t<ttl>] [-v<verbosity>] <path/to/configfiles/>" << endl;
cout << " collectagent -h" << endl;
cout << endl;
cout << "Options:" << endl;
cout << " -m<host> MQTT listen address [default: " << LISTENHOST << ":" << LISTENPORT << "]" << endl;
cout << " -r<host> REST API listen address [default: " << RESTAPIHOST << ":" << RESTAPIPORT << "]" << endl;
cout << " -c<host> Cassandra host [default: " << CASSANDRAHOST << ":" << CASSANDRAPORT << "]" << endl;
cout << " -m<host> MQTT listen address [default: " << defaults.mqttListenAddress << "]" << endl;
cout << " -r<host> REST API listen address [default: " << defaults.restListenAddress << "]" << endl;
cout << " -c<host> Cassandra host [default: " << defaults.cassandraSettings.address << "]" << endl;
cout << " -u<username> Cassandra username [default: none]" << endl;
cout << " -p<password> Cassandra password [default: none]" << endl;
cout << " -t<ttl> Cassandra insert TTL [default: " << TTL << "]" << endl;
cout << " -t<ttl> Cassandra insert TTL [default: " << defaults.cassandraSettings.ttl << "]" << endl;
cout << " -v<level> Set verbosity of output [default: " << defaults.logLevelCmd << "]" << endl
<< " Can be a number between 5 (all) and 0 (fatal)." << endl;
cout << endl;
cout << " -d Daemonize" << endl;
cout << " -s Print message statistics" << endl;
cout << " -s Print message stats" <<endl;
cout << " -x Parse and print the config but do not actually start collectagent" << endl;
cout << " -h This help page" << endl;
cout << endl;
}
int main(int argc, char* const argv[]) {
cout << "CollectAgent " << VERSION << " (libdcdb " << DCDB::Version::getVersion() << ")" << endl << endl;
bool validateConfig = false;
try{
/*
* Catch SIGINT signals to allow for proper server shutdowns.
*/
signal(SIGINT, sigHandler);
/*
* Catch critical signals to allow for backtraces
*/
signal(SIGABRT, abrtHandler);
signal(SIGSEGV, abrtHandler);
signal(SIGTERM, abrtHandler);
// Checking if path to config is supplied
if (argc <= 1) {
cout << "Please specify a path to the config-directory" << endl << endl;
usage();
exit(EXIT_FAILURE);
}
// Defining options
const char* opts = "m:r:c:C:u:p:t:v:dDsxh";
// Same mechanism as in DCDBPusher - checking if help string is requested before loading config
char ret;
while ((ret = getopt(argc, argv, opts)) != -1) {
switch (ret)
{
case 'h':
usage();
exit(EXIT_FAILURE);
break;
default:
//do nothing (other options are read later on)
break;
}
}
initLogging();
auto cmdSink = setupCmdLogger();
Configuration config(argv[argc - 1]);
if( !config.readGlobal() ) {
LOG(fatal) << "Failed to read global configuration!";
exit(EXIT_FAILURE);
}
globalCA_t& settings = config.getGlobal();
/* Parse command line */
int ret;
std::string listenHost, cassandraHost, restApiHost, ttl;
std::string cassandraUser, cassandraPassword;
std::string listenHost, cassandraHost, restApiHost;
std::string listenPort, cassandraPort, restApiPort;
listenHost = LISTENHOST;
cassandraHost = CASSANDRAHOST;
restApiHost = RESTAPIHOST;
ttl = "0";
statistics = false;
while ((ret=getopt(argc, argv, "m:r:c:u:p:t:dDsh"))!=-1) {
optind = 1;
while ((ret=getopt(argc, argv, opts))!=-1) {
switch(ret) {
case 'm':
listenHost = optarg;
settings.mqttListenAddress = optarg;
break;
case 'r':
restApiHost = optarg;
settings.restListenAddress = optarg;
break;
case 'c':
cassandraHost = optarg;
settings.cassandraSettings.address = optarg;
break;
case 'u':
cassandraUser = optarg;
settings.cassandraSettings.username = optarg;
break;
case 'p': {
cassandraPassword = optarg;
settings.cassandraSettings.password = optarg;
// What does this do? Mask the password?
size_t pwdLen = strlen(optarg);
memset(optarg, 'x', (pwdLen >= 3) ? 3 : pwdLen);
if (pwdLen > 3) {
......@@ -274,14 +337,20 @@ int main(int argc, char* const argv[]) {
break;
}
case 't':
ttl = optarg;
settings.cassandraSettings.ttl = stoul(optarg);
break;
case 'v':
settings.logLevelCmd = translateLogLevel(stoi(optarg));
break;
case 'd':
case 'D':
dcdbdaemon();
settings.daemonize = 1;
break;
case 's':
statistics = true;
settings.statistics = 1;
break;
case 'x':
validateConfig = true;
break;
case 'h':
default:
......@@ -290,9 +359,71 @@ int main(int argc, char* const argv[]) {
}
}
auto fileSink = setupFileLogger(settings.tempDir, std::string("collectagent"));
//severity level may be overwritten (per option or config-file) --> set it according to globalSettings
fileSink->set_filter(boost::log::trivial::severity >= settings.logLevelFile);
cmdSink->set_filter(boost::log::trivial::severity >= settings.logLevelCmd);
/*
* Catch SIGINT and SIGTERM signals to allow for proper server shutdowns.
*/
signal(SIGINT, sigHandler);
signal(SIGTERM, sigHandler);
/*
* Catch critical signals to allow for backtraces
*/
signal(SIGABRT, abrtHandler);
signal(SIGSEGV, abrtHandler);
LOG_LEVEL vLogLevel = validateConfig ? LOG_LEVEL::info : LOG_LEVEL::debug;
LOG_VAR(vLogLevel) << "----- Configuration -----";
//print global settings in either case
LOG(info) << "Global Settings:";
LOG(info) << " MQTT-listenAddress: " << settings.mqttListenAddress;
LOG(info) << " CacheInterval: " << settings.cacheInterval << " [s]";
LOG(info) << " CleaningInterval: " << settings.cleaningInterval << " [s]";
LOG(info) << " MessageThreads: " << settings.messageThreads;
LOG(info) << " MessageSlots: " << settings.messageSlots;
LOG(info) << " Daemonize: " << (settings.daemonize ? "Enabled" : "Disabled");
LOG(info) << " Statistics: " << (settings.statistics ? "Enabled" : "Disabled");
LOG(info) << " Write-Dir: " << settings.tempDir;
LOG(info) << " Hierarchy: " << (settings.hierarchy!="" ? settings.hierarchy : "none");
LOG(info) << (validateConfig ? " Only validating config files." : " ValidateConfig: Disabled");
LOG(info) << "Cassandra Driver Settings:";
LOG(info) << " Address: " << settings.cassandraSettings.address;
LOG(info) << " TTL: " << settings.cassandraSettings.ttl;
LOG(info) << " NumThreadsIO: " << settings.cassandraSettings.numThreadsIo;
LOG(info) << " QueueSizeIO: " << settings.cassandraSettings.queueSizeIo;
LOG(info) << " CoreConnPerHost: " << settings.cassandraSettings.coreConnPerHost;
LOG(info) << " MaxConnPerHost: " << settings.cassandraSettings.maxConnPerHost;
LOG(info) << " MaxConcRequests: " << settings.cassandraSettings.maxConcRequests;
LOG(info) << " DebugLog: " << (settings.cassandraSettings.debugLog ? "Enabled" : "Disabled");
#ifdef SimpleMQTTVerbose
LOG(info) << " Username: " << settings.cassandraSettings.username;
LOG(info) << " Password: " << settings.cassandraSettings.password;
#else
LOG(info) << " Username and password not printed.";
#endif
LOG(info) << "RestAPI Settings:";
LOG(info) << " REST Server: " << settings.restListenAddress;
LOG_VAR(vLogLevel) << "----- End Configuration -----";
if (validateConfig)
return 0;
// Daemonizing the collectagent
if(settings.daemonize)
dcdbdaemon();
/*
* Parse hostnames for port specifications
*/
listenHost = string(settings.mqttListenAddress);
size_t pos = listenHost.find(":");
if (pos != string::npos) {
listenPort = listenHost.substr(pos+1);
......@@ -300,6 +431,8 @@ int main(int argc, char* const argv[]) {
} else {
listenPort = LISTENPORT;
}
cassandraHost = string(settings.cassandraSettings.address);
pos = cassandraHost.find(":");
if (pos != string::npos) {
cassandraPort = cassandraHost.substr(pos+1);
......@@ -307,6 +440,8 @@ int main(int argc, char* const argv[]) {
} else {
cassandraPort = CASSANDRAPORT;
}
restApiHost = string(settings.restListenAddress);
pos = restApiHost.find(":");
if (pos != string::npos) {
restApiPort = restApiHost.substr(pos+1);
......@@ -315,15 +450,19 @@ int main(int argc, char* const argv[]) {
restApiPort = RESTAPIPORT;
}
// Setting the size of the sensor cache
mySensorCache.setMaxHistory(settings.cacheInterval * 1000000000);
//Allocate and initialize connection to Cassandra.
dcdbConn = new DCDB::Connection(cassandraHost, atoi(cassandraPort.c_str()), settings.cassandraSettings.username, settings.cassandraSettings.password);
dcdbConn->setNumThreadsIo(settings.cassandraSettings.numThreadsIo);
dcdbConn->setQueueSizeIo(settings.cassandraSettings.queueSizeIo);
uint32_t params[3] = {settings.cassandraSettings.coreConnPerHost, settings.cassandraSettings.maxConnPerHost, settings.cassandraSettings.maxConcRequests};
dcdbConn->setBackendParams(params);
/*
* Allocate and initialize connection to Cassandra.
*/
DCDB::Connection* dcdbConn;
dcdbConn = new DCDB::Connection(cassandraHost, atoi(cassandraPort.c_str()), cassandraUser, cassandraPassword);
if (!dcdbConn->connect()) {
std::cout << "Cannot connect to Cassandra!" << std::endl;
LOG(fatal) << "Cannot connect to Cassandra!";
exit(EXIT_FAILURE);
}
......@@ -332,33 +471,29 @@ int main(int argc, char* const argv[]) {
*/
dcdbConn->initSchema();
/*
* Allocate the SensorDataStore.
*/
mySensorDataStore = new DCDB::SensorDataStore(dcdbConn);
mySensorConfig = new DCDB::SensorConfig(dcdbConn);
/*
* Set TTL for data store inserts if TTL > 0.
*/
uint64_t ttlInt;
std::istringstream ttlParser(ttl);
if (!(ttlParser >> ttlInt)) {
std::cout << "Invalid TTL!" << std::endl;
exit(EXIT_FAILURE);
}
if (ttlInt) {
mySensorDataStore->setTTL(ttlInt);
}
if (settings.cassandraSettings.ttl > 0)
mySensorDataStore->setTTL(settings.cassandraSettings.ttl);
mySensorDataStore->setDebugLog(settings.cassandraSettings.debugLog);
/*
* Start the MQTT Message Server.
*/
SimpleMQTTServer ms(listenHost, listenPort);
SimpleMQTTServer ms(listenHost, listenPort, settings.messageThreads, settings.messageSlots);
ms.setMessageCallback(mqttCallback);
ms.start();
cout << "MQTT Server running..." << std::endl;
LOG(info) << "MQTT Server running...";
/*
* Start the HTTP Server for the REST API
......@@ -372,7 +507,7 @@ int main(int argc, char* const argv[]) {
httpServer_t httpServer(httpOptions.address(restApiHost).port(restApiPort));
httpThread = std::thread([&httpServer] { httpServer.run(); });
cout << "HTTP Server running..." << std::endl;
LOG(info) << "HTTP Server running...";
/*
* Run (hopefully) forever...
......@@ -380,38 +515,52 @@ int main(int argc, char* const argv[]) {
keepRunning = 1;
timeval start, end;
double elapsed;
cout << "Collect Agent running..." << std::endl;
msgCtr = 0;
pmsgCtr = 0;
readingCtr = 0;
gettimeofday(&start, NULL);
uint64_t lastCleanup = start.tv_sec;
LOG(info) << "Collect Agent running...";
while(keepRunning) {
gettimeofday(&start, NULL);
if(start.tv_sec - lastCleanup > settings.cleaningInterval) {
uint64_t purged = mySensorCache.clean(settings.cleaningInterval * 1000000000);
lastCleanup = start.tv_sec;
if(purged > 0)
LOG(info) << "Cache: purged " << purged << " obsolete entries";
}
sleep(60);
/* not really thread safe but will do the job */
gettimeofday(&end, NULL);
elapsed = (end.tv_sec - start.tv_sec) * 1000.0;
elapsed += (end.tv_usec - start.tv_usec) / 1000.0;
float publish = msgCtr?(pmsgCtr*100.0)/msgCtr:0;
if (statistics) {
cout << "Message rate: " << (msgCtr/elapsed)*1000.0 << " messages/second (" << publish << "% PUBLISH)\n";
if (settings.statistics && keepRunning) {
LOG(info) << "Performance: " << (readingCtr/elapsed)*1000.0 << " inserts/s, " << (msgCtr/elapsed)*1000.0 << " messages/s (" << publish << "% PUBLISH)";
}
msgCtr = 0;
pmsgCtr = 0;
readingCtr = 0;
}
cout << "Stopping...\n";
LOG(info) << "Stopping...";
ms.stop();
cout << "MQTT Server stopped..." << std::endl;
LOG(info) << "MQTT Server stopped...";
httpServer.stop();