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

ProcFS plugin added

- Supports sampling of the vmstat, meminfo, and stat files in the proc virtual filesystem
- Basic functionality verified, extensive testing to come in the next days
- Readme and Makefile have been updated as well
parent 089a88c0
......@@ -17,7 +17,7 @@ CXXFLAGS = -std=c++11 -DBOOST_DATE_TIME_POSIX_TIME_STD_CONFIG -DBOOST_NETWORK_EN
LIBS = -L../deps/mosquitto_build/lib -L$(DCDBDEPLOYPATH)/lib/ -ldl -lmosquitto -lboost_system -lboost_thread -lboost_log_setup -lboost_log -lpthread -lcrypto -lssl -lcppnetlib-server-parsers -lcppnetlib-uri -rdynamic
OBJS = src/dcdbpusher.o src/Configuration.o src/MQTTPusher.o src/HttpsServer.o
PLUGINS = pdu sysfs ipmi bacnet snmp
PLUGINS = procfs pdu sysfs ipmi bacnet snmp
ifeq ($(OS),Darwin)
BACNET_PORT = bsd
LIBEXT = dylib
......@@ -92,6 +92,9 @@ libdcdbplugin_bacnet.$(LIBEXT): src/sensors/bacnet/BACnetSensorGroup.o src/senso
libdcdbplugin_snmp.$(LIBEXT): src/sensors/snmp/SNMPSensorGroup.o src/sensors/snmp/SNMPConnection.o src/sensors/snmp/SNMPConfigurator.o
$(CXX) $(LIBFLAGS)$@ -o $@ $^ -L$(DCDBDEPLOYPATH)/lib/ -lboost_log -lboost_system -lnetsnmp -lnetsnmpagent
libdcdbplugin_procfs.$(LIBEXT): src/sensors/procfs/ProcfsSensorGroup.o src/sensors/procfs/ProcfsParser.o src/sensors/procfs/ProcfsConfigurator.o
$(CXX) $(LIBFLAGS)$@ -o $@ $^ -L$(DCDBDEPLOYPATH)/lib/ -lboost_log -lboost_system
#libdcdbplugin_opa.$(LIBEXT): src/sensors/opa/OpaSensorGroup.o src/sensors/opa/OpaConfigurator.o
# $(CXX) $(LIBFLAGS)$@ -o $@ $^ -L$(DCDBDEPLOYPATH)/lib/ -lboost_log -lboost_system -lopamgt -libverbs -libumad -lssl
......@@ -19,7 +19,8 @@
6. [BACnet](#bacnet)
7. [OPA](#opa)
1. [counterData](#opaCounterData)
8. [Writing own plugins](#writingOwnPlugins)
8. [ProcFS](#procfs)
9. [Writing own plugins](#writingOwnPlugins)
3. [TODOS](#todos)
## Introduction <a name="introduction"></a>
......@@ -467,6 +468,24 @@ Possible values for cntData:
* linkDowned
* uncorrectableErrors
## ProcFS (/proc filesystem) <a name="procfs"></a>
The ProcFS plugin enables dcdbpusher to sample resource usage metrics from a variety of files in the /proc virtual filesystem generated by the Linux kernel. Each defined sensor group is assigned to a specific file, which is periodically parsed. Currently supported files for sampling are:
* /proc/vmstat: contains virtual memory-related usage metrics;
* /proc/meminfo: contains RAM memory-related usage metrics (note that some of the metrics overlap with /proc/vmstat);
* /proc/stat: contains CPU usage-related metrics, both at system and core levels.
Note that in the ProcFS plugin, MQTT topics are automatically generated at runtime depending on the number of metrics found in each parsed file. For this reason, please be careful when configuring the plugin so that its MQTT topics do not overlap with those of other plugins.
Explanation of the values specific for the ProcFS plugin:
| Value | Explanation |
|:----- |:----------- |
| type | The type of the file parsed by the sensor group. Can be either "vmstat", "meminfo", or "procstat"
| path | Path of the file, if different from the default path in the /proc filesystem
| mqttPart | The mqttPart works similarly as in the Perf-event plugin. For sensors associated to metrics that are core-specific (i.e. some of those in /proc/stat) the mqttPart is replaced with the CPU id. For all other metrics that are system-wide, the mqttPart is used as it is.
| mqttStart | Base MQTT suffix that is automatically incremented to generate topics for sensors associated to metrics in the same file. Note that mqttPart and mqttStart, when combined, must form a string of 4 hex characters, or 16 bits.
## Writing own plugins <a name="writingOwnPlugins"></a>
First make sure you read the [plugins](#plugins) section.
Try out the `pluginGenerator/generatePlugin.sh` script!
......
global {
mqttPrefix /FF112233445566778899AABB
}
template_file def1 {
interval 1000
minValues 3
mqttPart 00
mqttStart E0
}
file vmstat {
default def1
path /Users/di52bul/dcdb/vmstat
type vmstat
mqttPart B0
mqttStart 00
}
file meminfo {
default def1
path /Users/di52bul/dcdb/meminfo
type meminfo
mqttPart C0
mqttStart 00
}
file procstat {
default def1
path /Users/di52bul/dcdb/stat
type procstat
mqttPart D0
mqttStart 00
}
/*
* ProcfsConfigurator.cpp
*
* Created on: 18.10.2018
* Author: Alessio Netti
*/
#include "ProcfsConfigurator.h"
/**
* Class constructor.
*
*/
ProcfsConfigurator::ProcfsConfigurator() {
_groupName = "file";
_baseName = "INVALID";
_entityName = "INVALID";
}
/**
* Class destructor.
*
*/
ProcfsConfigurator::~ProcfsConfigurator() {}
/**
* Increases by a certain value the input MQTT hex topic.
*
* Example: a mqtt="AAB7" and val=5 produce "AAC2" as output.
*
* @param mqtt: the MQTT hex string whose value has to be increased
* @param val: the value by which mqtt has to be increased
*
* @return the increased MQTT string
*
*/
const std::string ProcfsConfigurator::increaseMqtt(const std::string& mqtt, int val) {
unsigned long mqttDigits = stoul(mqtt, 0, 16);
mqttDigits += val;
std::stringstream stream;
stream << std::setfill ('0') << std::setw(mqtt.length()) << std::uppercase << std::hex << mqttDigits;
return stream.str();
}
/**
* Formats a numerical CPU core ID into a hex string of specified length.
*
* Example: a mqttPart="xx" and val=11 produce "0B" as output.
*
* @param mqttPart: a template MQTT string, defines the length of the final string
* @param val: the value of the CPU core ID
*
* @return the hex string representation of the input CPU core ID
*
*/
const std::string ProcfsConfigurator::formatMqttCPU(const std::string& mqttPart, unsigned int val) {
std::stringstream stream;
stream << std::setfill ('0') << std::setw(mqttPart.length()) << std::uppercase << std::hex << val;
return stream.str();
}
/**
* Reads a configuration file and instantiates Procfs Sensor Groups accordingly.
*
* Unlike in ConfiguratorTemplate, the assignment of MQTT topics to sensor is done in the
* sensorGroup() method.
*
* @param cfgPath: the path to the configuration file to be used
* @return true if successful, false otherwise
*
*/
bool ProcfsConfigurator::readConfig(std::string cfgPath) {
_cfgPath = cfgPath;
boost::property_tree::iptree cfg;
boost::property_tree::read_info(cfgPath, cfg);
// Read global variables (if present overwrite those from global.conf)
readGlobal(cfg);
// Read groups and templates for groups. Entities and single sensors are ignored as they are not supported in this plugin
BOOST_FOREACH(boost::property_tree::iptree::value_type &val, cfg) {
// Read template group
if (boost::iequals(val.first, "template_" + _groupName)) {
LOG(debug) << "Template " << _groupName << " \"" << val.second.data() << "\"";
if (!val.second.empty()) {
ProcfsSensorGroup* group = new ProcfsSensorGroup(val.second.data());
if (readSensorGroup(*group, val.second)) {
auto ret = _templateSensorGroups.insert(std::pair<std::string, ProcfsSensorGroup*>(val.second.data(), group));
if(!ret.second) {
LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting...";
}
} else {
LOG(warning) << "Template " << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
}
}
// Read sensor group
} else if (boost::iequals(val.first, _groupName)) {
LOG(debug) << _groupName << " \"" << val.second.data() << "\"";
if (!val.second.empty()) {
ProcfsSensorGroup* group = new ProcfsSensorGroup(val.second.data());
if (readSensorGroup(*group, val.second)) {
storeSensorGroup(group);
} else {
LOG(warning) << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
}
}
}
}
return true;
}
/**
* Configures a ProcfsSensorGroup object instantiated by the readSensorGroup method.
*
* For simplicity, the assignment of MQTT topics to sensors is done here and not in readConfig(),
* unlike in ConfiguratorTemplate.
*
* @param sGroup: the ProcfsSensorGroup object to be configured
* @param config: the BOOST property (sub-)tree containing configuration options for the sensor group
*
*/
void ProcfsConfigurator::sensorGroup(ProcfsSensorGroup& sGroup, CFG_VAL config) {
// Read additional configuration parameters supported by the Procfs plugin
std::string filePath="", fileType="", mqttStart="00";
BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
if (boost::iequals(val.first, "type")) {
fileType = val.second.data();
} else if (boost::iequals(val.first, "path")) {
filePath = val.second.data();
} else if (boost::iequals(val.first, "mqttStart")) {
mqttStart = val.second.data();
}
}
ProcfsParser *parser;
// The "type" parameter must refer to either vmstat, procstat or meminfo. If not, the sensor group is not initialized
// The only other case is when an empty string is found, which can happen for template groups
if(fileType == "")
return;
else if(fileType == "vmstat")
parser = new VmstatParser(filePath);
else if(fileType == "procstat")
parser = new ProcstatParser(filePath);
else if(fileType == "meminfo")
parser = new MeminfoParser(filePath);
else {
LOG(warning) << _groupName << " " << sGroup.getGroupName() << "::" << "Unspecified or invalid type! Available types are vmstat, meminfo, procstat";
return; }
// Probing the target file to be monitored and counting the available metrics
parser->init();
std::vector<std::string> *availableMetrics = parser->readNames();
int numMetrics = parser->getNumMetrics();
// If no metrics were found in the file (or the file is unreadable) the configuration aborts
if(numMetrics == 0) {
LOG(warning) << _groupName << " " << sGroup.getGroupName() << "::" << "Unable to parse file " << filePath << "!";
return;
}
LOG(debug) << " Number of metrics found: " << numMetrics;
// Vector that keeps track of the current number of metrics for each CPU core for which a MQTT topic has been assigned
// All core-independent metrics refer to position 0 in the vector
std::vector<unsigned int> *metricsCounter = new std::vector<unsigned int>(parser->getNumCPUs() + 1);
// Reading the number of CPU cores present in the system (if needed by the sensor group)
std::vector<short> *metricsCores = parser->readCPUs();
for(int i=0;i < metricsCounter->size(); i++) metricsCounter->at(i) = 0;
// Adding the sensors corresponding to availablMetrics
for(int i=0;i < numMetrics; i++) {
ProcfsSensorBase* sensor = new ProcfsSensorBase(availableMetrics->at(i));
// If the metric does not refer to a specific CPU core, the topic is prefix + default mqttPart + suffix
// The suffix is increased automatically through the metricsCounter vector
if( metricsCores == NULL || ( metricsCores != NULL && metricsCores->at(i) == -1 )) {
sensor->setMqtt(_mqttPrefix + sGroup.getMqttPart() + this->increaseMqtt(mqttStart, metricsCounter->at(0)));
metricsCounter->at(0)++;
}
// If the metrics refers to a specific CPU core, the topic is prefix + CPU core ID + suffix
// The suffix is automatically increased for each core through the metricsCounter vector
else {
// setMqtt is used twice for readability
sensor->setMqtt(this->increaseMqtt(mqttStart, metricsCounter->at(metricsCores->at(i) + 1)));
sensor->setMqtt(_mqttPrefix + this->formatMqttCPU(sGroup.getMqttPart(), metricsCores->at(i)) + sensor->getMqtt());
metricsCounter->at(metricsCores->at(i) + 1)++;
}
sGroup.pushBackSensor(sensor);
}
// De-allocating memory and restoring the parser to its default state
delete metricsCounter;
parser->close();
// Assigning the parser to the sensor group
sGroup.setParser(parser);
sGroup.setType(fileType);
}
/*
* ProcfsConfigurator.h
*
* Created on: 17.10.2018
* Author: Alessio Netti
*/
#ifndef PROCFSCONFIGURATOR_H_
#define PROCFSCONFIGURATOR_H_
#include <iostream>
#include <sstream>
#include <iomanip>
#include <vector>
#include "ProcfsSensorGroup.h"
#include "../../includes/ConfiguratorTemplate.h"
/**
* Class used to configure and instantiate Procfs plugins, using config files.
*
*/
class ProcfsConfigurator : public ConfiguratorTemplate<ProcfsSensorBase, ProcfsSensorGroup> {
public:
// Constructor and destructor
ProcfsConfigurator();
virtual ~ProcfsConfigurator();
protected:
// There are no sensor or sensor entities to be read from the config file, only groups
// Therefore, we only deal with the sensorGroup method from ConfiguratorTemplate
void sensorGroup(ProcfsSensorGroup& sGroup, CFG_VAL config) override;
void sensorBase(ProcfsSensorBase& s, CFG_VAL config) override {}
// The readConfig method has to be overridden as the MQTT topic logic is custom
bool readConfig(std::string cfgPath) override;
// Copy-paste of the method in the perfevent plugin, used to increase MQTT topics
const std::string increaseMqtt(const std::string& mqtt, int val);
// Similar to the above, formats a numerical CPU core ID into an hex string
const std::string formatMqttCPU(const std::string& mqttPart, unsigned int val);
};
// Functions required to correctly generate Configurator objects from linked libraries
extern "C" ConfiguratorInterface* create() {
return new ProcfsConfigurator;
}
extern "C" void destroy(ConfiguratorInterface* c) {
delete c;
}
#endif /* PROCFSCONFIGURATOR_H_ */
//
// ProcfsParser.cpp
// dcdbpusher
//
// Created by Netti, Alessio on 15.10.18.
// Copyright © 2018 LRZ. All rights reserved.
//
#include "ProcfsParser.h"
// Definitions for the ProcfsParser abstract class
/**
* Class constructor.
*
* @param path: string containing the path to the file to be parsed
*
*/
ProcfsParser::ProcfsParser(std::string path) {
this->_path = path;
this->_numMetrics = 0;
this->_numCPUs = 0;
this->_metricsNames = NULL;
this->_metricsReadings = NULL;
this->_metricsCores = NULL;
this->_initialized = false;
this->_metricsFile = NULL;
this->_stringBuffer = NULL;
this->_chars_read = 0;
}
/**
* Class destructor.
*
*/
ProcfsParser::~ProcfsParser() { this->close(); }
/**
* Initializes the parser.
*
* If a file can be opened at the specified path, and metrics can be successfully parsed and read, the parser is flagged as initialized.
* If any of this fails, the parser remains in its un-initialized state.
*
* @return true if initialization was successful, false otherwise
*/
bool ProcfsParser::init() {
if( this->_initialized == true )
return true;
if ((this->_metricsFile = fopen(this->_path.c_str(), "r")) == NULL)
return false;
// For the parser to be initialized, the metrics' names must be successfully read, together with the corresponding initial readings
this->_initialized = this->_readNames() != NULL && this->_readMetrics() != NULL;
return this->_initialized;
}
/**
* Closes the parser.
*
* The open file handle is closed, all memory is released, and the parser returns to a un-initialized state.
*
*/
void ProcfsParser::close() {
if( !this->_initialized )
return;
if( this->_metricsFile != NULL) fclose(this->_metricsFile);
this->_metricsFile = NULL;
this->_metricsNames->clear();
this->_metricsReadings->clear();
if( this->_stringBuffer != NULL ) free(this->_stringBuffer);
if( this->_metricsNames != NULL ) delete this->_metricsNames;
if( this->_metricsReadings ) delete this->_metricsReadings;
if( this->_metricsCores != NULL ) delete this->_metricsCores;
this->_stringBuffer = NULL;
this->_metricsNames = NULL;
this->_metricsReadings = NULL;
this->_metricsCores = NULL;
this->_numMetrics = 0;
this->_numCPUs = 0;
this->_initialized = false;
}
/**
* Returns the vector of strings representing the name of each metric in the target file.
*
* The parser must have been initialized earlier through the init() method.
*
* @return Pointer to a vector of strings if the parser is initialized, NULL otherwise
*/
std::vector<std::string> *ProcfsParser::readNames() {
if( !this->_initialized )
return NULL;
return this->_readNames();
}
/**
* Returns the vector of values for the readings of metrics in the target file.
*
* The parser must have been initialized earlier through the init() method.
*
* @return Pointer to a vector of reading_t structures if the parser is initialized and no errors are encountered, NULL otherwise
*/
std::vector<reading_t> *ProcfsParser::readMetrics() {
if( !this->_initialized )
return NULL;
return this->_readMetrics();
}
/**
* Returns the vector of values representing the CPU core associated to each metric (if any).
*
* Unlike in readNames() and readMetrics(), here we return directly the vector of CPU core IDs instead of calling a private method.
* The reading of CPU IDs is done in _readNames(), if necessary. Where not required, NULL is returned.
*
* @return Pointer to a vector of short integers containing the CPU core of each metric, if any, and NULL otherwise
*/
std::vector<short> *ProcfsParser::readCPUs() {
if( !this->_initialized )
return NULL;
return this->_metricsCores;
}
// **************************************************************
// Definitions for the VmstatParser class
/**
* Returns the vector of strings representing the name of each metric in the target vmstat (or meminfo) file.
*
* This private method does not perform any check on the status of the parser, and is meant to only contain the parsing logic.
* As vmstat and meminfo do not contain CPU-related information, the _metricsCores vector is left NULL.
*
* @return Pointer to a vector of strings
*/
std::vector<std::string> *VmstatParser::_readNames() {
if ( this->_metricsNames == NULL ) {
this->_metricsNames = new std::vector<std::string>();
char *lineToken, *savePtr;
fseek(this->_metricsFile, 0, SEEK_SET);
// Reading the target file line by line
while( getline(&_stringBuffer, &_chars_read, this->_metricsFile) > 0 ) {
savePtr = NULL;
// strtok splits the line in delimiter-separated tokens
lineToken = strtok_r(this->_stringBuffer, this->LINE_SEP, &savePtr);
if( lineToken == NULL ) {
// Found a malformed line, aborting
this->_metricsNames->clear();
break;
}
this->_metricsNames->push_back(std::string(lineToken));
}
this->_numMetrics = this->_metricsNames->size();
}
return this->_metricsNames;
}
/**
* Returns the vector of values for the readings of metrics in the target vmstat (or meminfo) file.
*
* This private method does not perform any check on the status of the parser, and is meant to only contain the parsing logic.
*
* @return Pointer to a vector of reading_t structures if no errors are encountered, NULL otherwise
*/
std::vector<reading_t> *VmstatParser::_readMetrics() {
if ( this->_metricsReadings == NULL )
this->_metricsReadings = new std::vector<reading_t>(this->_numMetrics);
fseek(this->_metricsFile, 0, SEEK_SET);
int ctr = 0;
char *lineToken, *savePtr;
// Reading the target file line by line
// Any changes in the file compared to when initialization was performed cause the method to return NULL
while( getline(&_stringBuffer, &_chars_read, this->_metricsFile) > 0 ) {
// Error: there are more lines/metrics than those counted with _readNames() upon initialization
if( ctr >= this->_numMetrics )
return NULL;
savePtr = NULL;
lineToken = strtok_r(this->_stringBuffer, this->LINE_SEP, &savePtr);
// First token is discarded, represents the metric's name
if(lineToken == NULL) return NULL;
// Second token is the actual metric
lineToken = strtok_r(NULL, this->LINE_SEP, &savePtr);
if(lineToken == NULL) return NULL;
try { this->_metricsReadings->at(ctr++).value = std::stoll(lineToken); }
catch (const std::invalid_argument& ia) { return NULL; }
catch (const std::out_of_range& oor) { return NULL; }
}
// Error: the number of read metrics is lower than the number of counted metrics upon initialization
if( ctr < this->_numMetrics ) return NULL;
return this->_metricsReadings;
}
// **************************************************************
// Definitions for the ProcstatParser class
/**
* Returns the vector of strings representing the name of each metric in the target stat file.
*
* This private method does not perform any check on the status of the parser, and is meant to only contain the parsing logic.
* Additionally, this method computes for each metric the associated CPU core (if any), which can be inferred from the metric name iself.
*
* @return Pointer to a vector of strings
*/
std::vector<std::string> *ProcstatParser::_readNames() {
if ( this->_metricsNames == NULL ) {
this->_metricsNames = new std::vector<std::string>();
this->_metricsCores = new std::vector<short>();
char *lineToken, *savePtr;
short currCPU = -1;
fseek(this->_metricsFile, 0, SEEK_SET);
// Reading the target file line by line
while( getline(&_stringBuffer, &_chars_read, this->_metricsFile) > 0 ) {
savePtr = NULL;
// First token in the line is the metric's name
lineToken = strtok_r(this->_stringBuffer, this->LINE_SEP, &savePtr);
if( lineToken == NULL ) {
// Found a malformed line, aborting
this->_metricsNames->clear();
this->_metricsCores->clear();
this->_numCPUs = 0;
break;
}
// We check whether the line is cpu core-related or not through a regular expression
// If the name contains the "cpu" keyword, then it is cpu-related
if( std::regex_search(lineToken, this->_match, this->reg_exp) ) {
// If a match to an integer number is found within the name, the metric is related to a specific core. Otherwise,
// it is considered as a system-wide metric
try { currCPU = !std::regex_search(lineToken, this->_match, reg_exp_num) ? -1 : (short)std::stol(this->_match.str(0)); }
catch (const std::invalid_argument& ia) { return NULL; }
catch (const std::out_of_range& oor) { return NULL; }
// The number of CPU cores in the system is inferred from the maximum CPU ID found while parsing
if( currCPU >= this->_numCPUs ) this->_numCPUs = currCPU + 1;
std::string cpuName(lineToken);
this->_actualMetrics = 0;
while( (lineToken = strtok_r(this->_stringBuffer, this->LINE_SEP, &savePtr)) != NULL && this->_actualMetrics < this->DEFAULTMETRICS ) {
this->_metricsNames->push_back(cpuName + "_" + this->DEFAULT_NAMES[this->_actualMetrics]);
this->_metricsCores->push_back(currCPU);
// This variable counts the number of metrics for each CPU that are effectively parsed from the stat file
// Current maximum allowed is 10 metrics, any additional metrics are discarded
this->_actualMetrics++;
}
}
// If the metric is not cpu core-related, it is simply pushed to the _metricsNames vector
else {
currCPU = -1;
this->_metricsNames->push_back(std::string(lineToken));
this->_metricsCores->push_back(currCPU);
}
}
this->_numMetrics = this->_metricsNames->size();
}
return this->_metricsNames;
}
/**
* Returns the vector of values for the readings of metrics in the target stat file.
*
* This private method does not perform any check on the status of the parser, and is meant to only contain the parsing logic.
*
* @return Pointer to a vector of reading_t structures if no errors are encountered, NULL otherwise
*/
std::vector<reading_t> *ProcstatParser::_readMetrics() {
if ( this->_metricsReadings == NULL )
this->_metricsReadings = new std::vector<reading_t>(this->_numMetrics);
fseek(this->_metricsFile, 0, SEEK_SET);