From 899592c8ad5df6f960457d96757ae8e3339919cc Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Tue, 7 Jun 2016 13:35:16 +0100 Subject: [PATCH] Update InfluxDB line formatting Fixes a couple issues to do with line formatting of influx DB data points. All keys and values need commas and white space escaping. Values are also checked for type. If a numeric or scientific value is detected this is output as an Influx floating point/scientific number. Booleans are detected and output in a canonical format. All other values are strings, which have double quotes escaped and the entire string is wrapped in double quotes. The handling of thresholds has changed before this becomes officially released. These values if available are passed to the accumulation function in a dictionary, said dictionary builds a single data point with multiple fields, rather than the existing 5 data points, thus saving bandwidth costs. fixes #11904 Signed-off-by: Gunnar Beutner --- doc/6-object-types.md | 47 +++++++++++++++++-- lib/perfdata/influxdbwriter.cpp | 82 +++++++++++++++++++++++++-------- lib/perfdata/influxdbwriter.hpp | 7 ++- 3 files changed, 110 insertions(+), 26 deletions(-) diff --git a/doc/6-object-types.md b/doc/6-object-types.md index 9921c0608..3dc32601e 100644 --- a/doc/6-object-types.md +++ b/doc/6-object-types.md @@ -882,10 +882,10 @@ Example: } Measurement names and tags are fully configurable by the end user. The InfluxdbWriter -object will automatically add a `metric` and `type` tag to each data point. These -correlate to perfdata label and perfdata field (value, warn, crit, min, max) respectively. -If a value associated with a tag is not able to be resolved, it will be dropped and not -sent to the target host. +object will automatically add a `metric` tag to each data point. This correlates to the +perfdata label. Fields (value, warn, crit, min, max) are created from data if available +and the configuration allows it. If a value associated with a tag is not able to be +resolved, it will be dropped and not sent to the target host. The database is assumed to exist so this object will make no attempt to create it currently. @@ -908,6 +908,45 @@ Configuration Attributes: flush_interval | **Optional.** How long to buffer data points before transfering to InfluxDB. Defaults to `10s`. flush_threshold | **Optional.** How many data points to buffer before forcing a transfer to InfluxDB. Defaults to `1024`. +### Instance Tagging + +Consider the following service check: + + apply Service "disk" for (disk => attributes in host.vars.disks) { + import "generic-service" + check_command = "disk" + display_name = "Disk " + disk + vars.disk_partitions = disk + assign where host.vars.disks + } + +This is a typical pattern for checking individual disks, NICs, SSL certificates etc associated +with a host. What would be useful is to have the data points tagged with the specific instance +for that check. This would allow you to query time series data for a check on a host and for a +specific instance e.g. /dev/sda. To do this quite simply add the instance to the service variables: + + apply Service "disk" for (disk => attributes in host.vars.disks) { + ... + vars.instance = disk + ... + } + +Then modify your writer configuration to add this tag to your data points if the instance variable +is associated with the service: + + object InfluxdbWriter "influxdb" { + ... + service_template = { + measurement = "$service.check_command$" + tags = { + hostname = "$host.name$" + service = "$service.name$" + instance = "$service.vars.instance$" + } + } + ... + } + ## LiveStatusListener Livestatus API interface available as TCP or UNIX socket. Historical table queries diff --git a/lib/perfdata/influxdbwriter.cpp b/lib/perfdata/influxdbwriter.cpp index a091c31fb..caf9e1dfa 100644 --- a/lib/perfdata/influxdbwriter.cpp +++ b/lib/perfdata/influxdbwriter.cpp @@ -45,6 +45,7 @@ #include #include #include +#include using namespace icinga; @@ -140,8 +141,6 @@ void InfluxdbWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C double ts = cr->GetExecutionEnd(); // Clone the template and perform an in-place macro expansion of measurement and tag values - // Work Needed: Escape ' ', ',' and '=' in field keys, tag keys and tag values - // Quote field values when the type is string Dictionary::Ptr tmpl_clean = service ? GetServiceTemplate() : GetHostTemplate(); Dictionary::Ptr tmpl = static_pointer_cast(tmpl_clean->Clone()); tmpl->Set("measurement", MacroProcessor::ResolveMacros(tmpl->Get("measurement"), resolvers, cr)); @@ -158,16 +157,10 @@ retry: } } - // If the service was appiled via a 'apply Service for' command then resolve the - // instance and add it as a tag (e.g. check_command = mtu, name = mtueth0, instance = eth0) - if (service && (service->GetName() != service->GetCheckCommand()->GetName())) { - tags->Set("instance", service->GetName().SubStr(service->GetCheckCommand()->GetName().GetLength())); - } - SendPerfdata(tmpl, cr, ts); } -void InfluxdbWriter::SendPerfdata(const Dictionary::Ptr tmpl, const CheckResult::Ptr& cr, double ts) +void InfluxdbWriter::SendPerfdata(const Dictionary::Ptr& tmpl, const CheckResult::Ptr& cr, double ts) { Array::Ptr perfdata = cr->GetPerformanceData(); @@ -190,37 +183,86 @@ void InfluxdbWriter::SendPerfdata(const Dictionary::Ptr tmpl, const CheckResult: } } - SendMetric(tmpl, pdv->GetLabel(), "value", pdv->GetValue(), ts); - + Dictionary::Ptr fields = new Dictionary(); + fields->Set(String("value"), pdv->GetValue()); if (GetEnableSendThresholds()) { if (pdv->GetCrit()) - SendMetric(tmpl, pdv->GetLabel(), "crit", pdv->GetCrit(), ts); + fields->Set(String("crit"), pdv->GetCrit()); if (pdv->GetWarn()) - SendMetric(tmpl, pdv->GetLabel(), "warn", pdv->GetWarn(), ts); + fields->Set(String("warn"), pdv->GetWarn()); if (pdv->GetMin()) - SendMetric(tmpl, pdv->GetLabel(), "min", pdv->GetMin(), ts); + fields->Set(String("min"), pdv->GetMin()); if (pdv->GetMax()) - SendMetric(tmpl, pdv->GetLabel(), "max", pdv->GetMax(), ts); + fields->Set(String("max"), pdv->GetMax()); } + + SendMetric(tmpl, pdv->GetLabel(), fields, ts); } } -void InfluxdbWriter::SendMetric(const Dictionary::Ptr tmpl, const String& label, const String& type, double value, double ts) +String InfluxdbWriter::EscapeKey(const String& str) +{ + // Iterate over the key name and escape commas and spaces with a backslash + String result = str; + boost::algorithm::replace_all(result, ",", "\\,"); + boost::algorithm::replace_all(result, " ", "\\ "); + return str; +} + +String InfluxdbWriter::EscapeField(const String& str) +{ + // Technically everything entering here from PerfdataValue is a + // double, but best have the safety net in place. + + // Handle numerics + boost::regex numeric("-?\\d+(\\.\\d+)?((e|E)[+-]?\\d+)?"); + if (boost::regex_match(str.GetData(), numeric)) { + return str; + } + + // Handle booleans + boost::regex boolean_true("t|true", boost::regex::icase); + if (boost::regex_match(str.GetData(), boolean_true)) + return "true"; + boost::regex boolean_false("f|false", boost::regex::icase); + if (boost::regex_match(str.GetData(), boolean_false)) + return "false"; + + // Otherwise it's a string and needs escaping and quoting + String result = str; + boost::algorithm::replace_all(result, "\"", "\\\""); + return "\"" + result + "\""; +} + +void InfluxdbWriter::SendMetric(const Dictionary::Ptr& tmpl, const String& label, const Dictionary::Ptr& fields, double ts) { std::ostringstream msgbuf; - msgbuf << tmpl->Get("measurement"); + msgbuf << EscapeKey(tmpl->Get("measurement")); Dictionary::Ptr tags = tmpl->Get("tags"); if (tags) { ObjectLock olock(tags); BOOST_FOREACH(const Dictionary::Pair& pair, tags) { // Empty macro expansion, no tag - if (!pair.second.IsEmpty()) - msgbuf << "," << pair.first << "=" << pair.second; + if (!pair.second.IsEmpty()) { + msgbuf << "," << EscapeKey(pair.first) << "=" << EscapeKey(pair.second); + } } } - msgbuf << ",metric=" << label << ",type=" << type << " value=" << value << " " << static_cast(ts); + msgbuf << ",metric=" << label << " "; + + bool first = true; + ObjectLock fieldLock(fields); + BOOST_FOREACH(const Dictionary::Pair& pair, fields) { + if (first) + first = false; + else + msgbuf << ","; + msgbuf << EscapeKey(pair.first) << "=" << EscapeField(pair.second); + } + + msgbuf << " " << static_cast(ts); Log(LogDebug, "InfluxdbWriter") << "Add to metric list:'" << msgbuf.str() << "'."; diff --git a/lib/perfdata/influxdbwriter.hpp b/lib/perfdata/influxdbwriter.hpp index a241f08c0..22b806012 100644 --- a/lib/perfdata/influxdbwriter.hpp +++ b/lib/perfdata/influxdbwriter.hpp @@ -54,11 +54,14 @@ private: Array::Ptr m_DataBuffer; void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void SendPerfdata(const Dictionary::Ptr tmpl, const CheckResult::Ptr& cr, double ts); - void SendMetric(const Dictionary::Ptr tmpl, const String& label, const String& type, double value, double ts); + void SendPerfdata(const Dictionary::Ptr& tmpl, const CheckResult::Ptr& cr, double ts); + void SendMetric(const Dictionary::Ptr& tmpl, const String& label, const Dictionary::Ptr& fields, double ts); void FlushTimeout(void); void Flush(void); + static String EscapeKey(const String& str); + static String EscapeField(const String& str); + Stream::Ptr Connect(void); }; -- 2.49.0