Commit 6151b39f authored by Alessio Netti's avatar Alessio Netti
Browse files

ProcFS plugin overhaul

- Reflects changes applied in the master branch
parent b44d4e66
......@@ -475,7 +475,9 @@ The ProcFS plugin enables dcdbpusher to sample resource usage metrics from a var
* /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.
Note that the ProcFS plugin can operate in two distinct modes, with respect to MQTT topics:
* Automatic: if no sensors are specified, all metrics discovered in the underlying parsed file are acquired; sensors and MQTT topics are generated for them. Please be careful when configuring the plugin so that its MQTT topics do not overlap with those of other plugins.
* Manual: If at least one sensor is specified, only the corresponding metrics are acquired, and all other metrics in the parsed file are discarded. MQTT topics are assigned accordingly, using the mqttPrefix, mqttPart and mqttSuffix fields.
Explanation of the values specific for the ProcFS plugin:
......@@ -483,8 +485,30 @@ Explanation of the values specific for the ProcFS plugin:
|:----- |:----------- |
| 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.
| mqttPart | The mqttPart works similarly as in the Perf-event plugin. For sensors associated to metrics that are core-specific (e.g. 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 this parameter is used only if automatic MQTT topic generation is enabled, when no sensors are explicitly defined.
| cpus | Defines the set of CPU cores for which metrics must be collected. Only affects extraction of core-specific metrics (e.g. those in /proc/stat), whereas system-level metrics are acquired regardless of this setting. If no CPU cores set is defined, metrics for all available CPU cores will be collected. This parameter follows the same syntax as in the Perf-event plugin.
Additionally, sensors in the ProcFS plugin (defined with the "metric" keyword) support the following additional values:
| Value | Explanation |
|:----- |:----------- |
| type | The type of the specific metric associated to the sensor. This field must match the exact name of a metric in the underlying parsed file. If such a match does not exist, the sensor is discarded.
| perCpu | Boolean. If set to "on", the metric will be collected for each CPU core specified with the "cpus" sensor group parameter, or for all CPU cores if none is specified. Otherwise, the metric will be collected only at system level. This parameter has no effect on metrics that are not acquired at CPU core level (e.g. those in /proc/vmstat and /proc/meminfo).
The "type" field can be inferred for each sensor by simply checking the underlying file parsed by the sensor group. For /proc/stat files, on the other hand, CPU core-related metrics are collected in separate columns, which adopt the following naming scheme that can be used to define sensors:
* col_user
* col_nice
* col_system
* col_idle
* col_iowait
* col_irq
* col_softirq
* col_steal
* col_guest
* col_guest_nice
Additional CPU-related metrics (that may be introduced in future versions of the Linux kernel) are not supported by the DCDB ProcFS plugin.
## Writing own plugins <a name="writingOwnPlugins"></a>
First make sure you read the [plugins](#plugins) section.
......
global {
mqttPrefix /FF112233445566778899AABB
mqttPrefix /FF112233445566778899AAB
}
template_file def1 {
interval 1000
minValues 3
mqttPart 00
mqttPart 000
mqttStart E0
}
file vmstat {
file meminfo {
default def1
path /Users/di52bul/dcdb/vmstat
type vmstat
mqttPart B0
path /proc/meminfo
type meminfo
mqttPart E00
mqttStart 00
metric MemFree {
type MemFree
mqttsuffix 36
skipConstVal on
}
metric AnonPages {
type AnonPages
mqttsuffix AA
skipConstVal on
}
metric Hugepagesize {
type Hugepagesize
mqttsuffix BB
skipConstVal on
}
}
file meminfo {
file vmstat {
default def1
path /Users/di52bul/dcdb/meminfo
type meminfo
mqttPart C0
path /proc/vmstat
type vmstat
mqttPart D00
mqttStart 00
metric nr_dirty_threshold {
type nr_dirty_threshold
mqttsuffix F0
skipConstVal on
}
metric nr_file_pages {
type nr_file_pages
mqttsuffix F1
skipConstVal on
}
metric nr_alloc_batch {
type nr_alloc_batch
mqttsuffix F2
skipConstVal on
}
}
file procstat {
default def1
path /Users/di52bul/dcdb/stat
path /proc/stat
type procstat
mqttPart D0
mqttPart C00
mqttStart 00
cpus 250,36
metric col_idle {
type col_idle
mqttsuffix 36
skipConstVal on
perCpu on
}
metric col_user {
type col_user
mqttsuffix 37
skipConstVal on
perCpu on
}
metric col_nice {
type col_nice
mqttsuffix 38
skipConstVal on
perCpu off
}
metric ctxt {
type ctxt
mqttsuffix 40
skipConstVal on
perCpu off
}
}
......@@ -13,7 +13,7 @@
*/
ProcfsConfigurator::ProcfsConfigurator() {
_groupName = "file";
_baseName = "INVALID";
_baseName = "metric";
_entityName = "INVALID";
}
......@@ -71,13 +71,13 @@ const std::string ProcfsConfigurator::formatMqttCPU(const std::string& mqttPart,
*/
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
......@@ -94,7 +94,7 @@ bool ProcfsConfigurator::readConfig(std::string cfgPath) {
LOG(warning) << "Template " << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
}
}
// Read sensor group
// Read sensor group
} else if (boost::iequals(val.first, _groupName)) {
LOG(debug) << _groupName << " \"" << val.second.data() << "\"";
if (!val.second.empty()) {
......@@ -110,6 +110,22 @@ bool ProcfsConfigurator::readConfig(std::string cfgPath) {
return true;
}
/**
* Configures a ProcfsSensorBase object instantiated by the readSensorBase method.
*
* @param s: the ProcfsSensorBase object to be configured
* @param config: the BOOST property (sub-)tree containing configuration options for the sensor
*
*/
void ProcfsConfigurator::sensorBase(ProcfsSensorBase& s, CFG_VAL config) {
BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
if (boost::iequals(val.first, "type"))
s.setMetric(val.second.data());
else if (boost::iequals(val.first, "perCPU"))
s.setPerCPU( val.second.data() == "on" );
}
}
/**
* Configures a ProcfsSensorGroup object instantiated by the readSensorGroup method.
*
......@@ -123,6 +139,11 @@ bool ProcfsConfigurator::readConfig(std::string cfgPath) {
void ProcfsConfigurator::sensorGroup(ProcfsSensorGroup& sGroup, CFG_VAL config) {
// Read additional configuration parameters supported by the Procfs plugin
std::string filePath="", fileType="", mqttStart="00";
// Set of cpu ids read during configuration
std::set<int> cpuSet;
std::vector<ProcfsSensorBase*> derivedSensors;
ProcfsParser *parser;
BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
if (boost::iequals(val.first, "type")) {
fileType = val.second.data();
......@@ -130,10 +151,11 @@ void ProcfsConfigurator::sensorGroup(ProcfsSensorGroup& sGroup, CFG_VAL config)
filePath = val.second.data();
} else if (boost::iequals(val.first, "mqttStart")) {
mqttStart = val.second.data();
} else if (boost::iequals(val.first, "cpus")) {
cpuSet = parseCpuString(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 == "")
......@@ -148,49 +170,109 @@ void ProcfsConfigurator::sensorGroup(ProcfsSensorGroup& sGroup, CFG_VAL config)
LOG(warning) << _groupName << " " << sGroup.getGroupName() << "::" << "Unspecified or invalid type! Available types are vmstat, meminfo, procstat";
return; }
// if any sensor objects specified by users in the configuration file are present,
// automatic MQTT topic assignment is disabled and the topics supplied in the config are used
bool autoMQTT = sGroup.getSensors().empty();
// After getting the vector of available metrics (subset of the sensor map, if any) from the parsed file, we delete
// all sensors not matching any parsed metrics, and arrange their order to respect the one in the file
derivedSensors = sGroup.getDerivedSensors();
parser->init(&derivedSensors, &cpuSet);
sGroup.setType(fileType);
// 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
int numMetrics = parser->getNumMetrics();
if(numMetrics == 0) {
LOG(warning) << _groupName << " " << sGroup.getGroupName() << "::" << "Unable to parse file " << filePath << "!";
return;
}
LOG(warning) << _groupName << " " << sGroup.getGroupName() << "::" << "Unable to parse file " << filePath << ", please check your configuration!";
sGroup.getSensors().clear();
sGroup.getDerivedSensors().clear();
return; }
LOG(debug) << " Number of metrics found: " << numMetrics;
// Assigning the parser to the sensor group
// We assign the final sensor objects as parsed in the target file, and assign the parser object
sGroup.replaceSensors(parser->getSensors());
sGroup.setParser(parser);
// 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
// All core-independent metrics refer to position 0 in the vector. Used only on automatic MQTT assignment
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
// Adding the sensors corresponding to availableMetrics
for(int i=0;i < numMetrics; i++) {
ProcfsSensorBase* sensor = new ProcfsSensorBase(availableMetrics->at(i));
ProcfsSensorBase *sensor = (ProcfsSensorBase*)sGroup.getSensors().at(i);
if ( autoMQTT ) sensor->setMqtt(this->increaseMqtt(mqttStart, metricsCounter->at(sensor->getCPUId() + 1)++ ));
// 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);
if( sensor->getCPUId() == -1 )
sensor->setMqtt(_mqttPrefix + sGroup.getMqttPart() + sensor->getMqtt());
// 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
sensor->setMqtt(_mqttPrefix + this->formatMqttCPU(sGroup.getMqttPart(), sensor->getCPUId()) + sensor->getMqtt());
LOG(debug) << sGroup.getGroupName() << "::" << sensor->getName() << " using MQTT-topic \"" << sensor->getMqtt() << "\"";
}
// De-allocating memory and restoring the parser to its default state
// De-allocating memory
delete metricsCounter;
parser->close();
cpuSet.clear();
//parser->close();
}
//TODO: move this up in ConfiguratorTemplate
std::set<int> ProcfsConfigurator::parseCpuString(const std::string& cpuString) {
std::set<int> cpus;
int maxCpu = 512;
std::vector<std::string> subStrings;
std::stringstream ssComma(cpuString);
std::string item;
while (std::getline(ssComma, item, ',')) {
subStrings.push_back(item);
}
for (auto s : subStrings) {
if (s.find('-') != std::string::npos) { //range of values (e.g. 1-5) specified
std::stringstream ssHyphen(s);
std::string min, max;
std::getline(ssHyphen, min, '-');
std::getline(ssHyphen, max);
try {
int minVal = stoi(min);
int maxVal = stoi(max);
for (int i = minVal; i <= maxVal; i++) {
if (i >= 0 && i < maxCpu) {
cpus.insert((int)i);
}
}
} catch (const std::exception& e) {
LOG(debug) << "Could not parse values \"" << min << "-" << max << "\"";
}
} else { //single value
try {
int val = stoi(s);
if (val >= 0 && val < maxCpu) {
cpus.insert((int)val);
}
} catch (const std::exception& e) {
LOG(debug) << "Could not parse value \"" << s << "\"";
}
}
}
if (cpus.empty()) {
LOG(warning) << " CPUs could not be parsed!";
} else {
std::stringstream sstream;
sstream << " CPUS: ";
for (auto i : cpus) {
sstream << i << ", ";
}
std::string msg = sstream.str();
msg.pop_back();
msg.pop_back();
LOG(debug) << msg;
}
return cpus;
}
......@@ -20,34 +20,36 @@
*
*/
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
// There are no sensor entities to be read from the config file, only groups
// Therefore, we only deal with the sensorGroup and sensorBase methods from ConfiguratorTemplate
void sensorGroup(ProcfsSensorGroup& sGroup, CFG_VAL config) override;
void sensorBase(ProcfsSensorBase& s, 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);
// Copy-paste of the method in the perfevent plugin to parse CPU id strings
std::set<int> parseCpuString(const std::string& cpuString);
};
// Functions required to correctly generate Configurator objects from linked libraries
extern "C" ConfiguratorInterface* create() {
return new ProcfsConfigurator;
return new ProcfsConfigurator;
}
extern "C" void destroy(ConfiguratorInterface* c) {
delete c;
delete c;
}
#endif /* PROCFSCONFIGURATOR_H_ */
This diff is collapsed.
......@@ -9,9 +9,12 @@
#ifndef PROCFSPARSER_H_
#define PROCFSPARSER_H_
#include "../../includes/SensorBase.h"
#include "ProcfsSensorBase.h"
#include "timestamp.h"
#include <boost/regex.hpp>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <string.h>
#include <exception>
......@@ -21,42 +24,45 @@
*
*/
class ProcfsParser {
public:
// Constructor and destructor
ProcfsParser(std::string path);
~ProcfsParser();
// Init/close methods
bool init();
bool init(std::vector<ProcfsSensorBase*> *sensorVec = NULL, std::set<int> *cpuSet = NULL);
virtual void close();
// Setters and getters
std::string getPath() { return this->_path; }
void setPath(std::string new_path) { this->_path = new_path; this->close(); }
unsigned int getNumMetrics() { return this->_numMetrics; }
unsigned int getNumCPUs() { return this->_numCPUs; }
// Public parsing methods
std::vector<std::string> *readNames();
std::vector<reading_t> *readMetrics();
std::vector<short> *readCPUs();
unsigned int getCacheIndex() { return this->_cacheIndex; }
void setCacheIndex(unsigned int c) { this->_cacheIndex = c; }
std::vector<ProcfsSensorBase*> *getSensors();
std::vector<ProcfsSensorBase*> *readSensors();
protected:
// Private parsing methods that implement all necessary logic to parse metrics' names (and associated CPU cores, if any)
// and values. MUST be overridden by children classes
virtual std::vector<std::string> *_readNames() { return NULL; };
virtual std::vector<reading_t> *_readMetrics() { return NULL; };
// Vectors containing the names of parsed metrics, associated CPU cores (if any), and values.
// After initialization, _metricsNames and _metricsReadings are always non-NULL.
// _metricsCores can be NULL if the file does not contain any CPU information (like in vmstat or meminfo)
std::vector<std::string> *_metricsNames;
std::vector<short> *_metricsCores;
std::vector<reading_t> *_metricsReadings;
virtual bool _readNames(std::map<std::string, ProcfsSensorBase*> *sensorMap, std::set<int> *cpuSet) { return NULL; };
virtual bool _readMetrics() { return NULL; };
// Vector containing the names of parsed metrics, associated CPU cores (if any), and values.
// After initialization, _sensors is always non-NULL.
std::vector<ProcfsSensorBase*> *_sensors;
// Keeps track of which lines and columns in the parsed file must be skipped
std::vector<bool> _skipLine;
// Every element in skipColumn can be either 0 (skip column), 1 (parse for all CPUs) or 2 (parse only at node level)
std::vector<char> _skipColumn;
// Internal variables
bool _initialized;
unsigned int _cacheIndex;
unsigned int _numMetrics;
unsigned int _numCPUs;
std::string _path;
......@@ -73,13 +79,13 @@ protected:
*
*/
class VmstatParser : public ProcfsParser {
public:
VmstatParser(const std::string path = "") : ProcfsParser(path) { this->LINE_SEP = " \t"; if(path == "") this->_path = "/proc/vmstat"; else this->_path = path; }
protected:
std::vector<std::string> *_readNames() override;
std::vector<reading_t> *_readMetrics() override;
bool _readNames(std::map<std::string, ProcfsSensorBase*> *sensorMap, std::set<int> *cpuSet) override;
bool _readMetrics() override;
};
// **************************************************************
......@@ -90,7 +96,7 @@ protected:
* (the ':' after the metrics' names) which can be handled by strtok without any alteration to the code
*/
class MeminfoParser : public VmstatParser {
public:
MeminfoParser(const std::string path = "") : VmstatParser(path) { this->LINE_SEP = " :\t"; if(path == "") this->_path = "/proc/meminfo"; else this->_path = path; }
};
......@@ -106,19 +112,22 @@ class ProcstatParser : public ProcfsParser {
public:
ProcstatParser(const std::string path = "") : ProcfsParser(path) { this->LINE_SEP = " \t"; if(path == "") this->_path = "/proc/stat"; else this->_path = path; }
protected:
std::vector<std::string> *_readNames() override;
std::vector<reading_t> *_readMetrics() override;
bool _readNames(std::map<std::string, ProcfsSensorBase*> *sensorMap, std::set<int> *cpuSet) override;
bool _readMetrics() override;
// Internal variables
unsigned short _actualMetrics;
unsigned short _numColumns;
boost::cmatch _match;
// The number of known metrics in each "cpu" line together with their names, as of Oct. 2018
enum { DEFAULTMETRICS = 10 };
const std::string DEFAULT_NAMES[DEFAULTMETRICS] = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"};
const std::string DEFAULT_NAMES[DEFAULTMETRICS] = {"col_user", "col_nice", "col_system", "col_idle", "col_iowait", "col_irq", "col_softirq", "col_steal", "col_guest", "col_guest_nice"};
// Regex to match strings beginning with the "cpu" keyword
const boost::regex reg_exp = boost::regex("^cpu");
// C strings that encode the prefixes of cpu-related lines
const char *_cpu_prefix = "cpu";
const short _cpu_prefix_len = strlen(this->_cpu_prefix);
// Regex to match strings ending with integer numbers (in this case, CPU core IDs)
const boost::regex reg_exp_num = boost::regex("[0-9]+$");
};
......
......@@ -11,15 +11,50 @@
#include "../../includes/SensorBase.h"
/**
* The ProcfsSensorBase class does nothing right now, compared to SensorBase.
* The ProcfsSensorBase class adds two private members, cpuID and metric, to the SensorBase class.
*
*/
class ProcfsSensorBase : virtual public SensorBase {
class ProcfsSensorBase : public SensorBase {
public:
// Constructor and destructor
ProcfsSensorBase(const std::string& name) : SensorBase(name) {}
ProcfsSensorBase(const std::string& name) : SensorBase(name) {
this->_metric = "";
this->_perCPU = false;
this->_cpuId = -1;
}
ProcfsSensorBase(const std::string& name, const std::string& metric, bool percpu = false, int cpuid = -1) : SensorBase(name) {
this->_metric = metric;
this->_perCPU = percpu;
this->_cpuId = cpuid;
}
// Copy constructor
ProcfsSensorBase(ProcfsSensorBase& other) : SensorBase(other) {
this->_metric = other.getMetric();
this->_perCPU = other.isPerCPU();
this->_cpuId = other.getCPUId();
}
virtual ~ProcfsSensorBase() {}
void setMetric(std::string m) { this->_metric = m; }
std::string getMetric() { return this->_metric; }
void setPerCPU(bool p) { this->_perCPU = p; }