[icinga-checkins] icinga.org: icinga2/fix/api-sync-repl-11684: Update InfluxDB line formatting

git at icinga.org git at icinga.org
Wed Jun 15 17:50:18 CEST 2016


Module: icinga2
Branch: fix/api-sync-repl-11684
Commit: 899592c8ad5df6f960457d96757ae8e3339919cc
URL:    https://git.icinga.org/?p=icinga2.git;a=commit;h=899592c8ad5df6f960457d96757ae8e3339919cc

Author: Simon Murray <spjmurray at yahoo.co.uk>
Date:   Tue Jun  7 13:35:16 2016 +0100

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 <gunnar.beutner at netways.de>

---

 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 9921c06..3dc3260 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`.
 
+### <a id="objecttype-influxdbwriter-instance-tags"></a> 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$"
+        }
+      }
+      ...
+    }
+
 ## <a id="objecttype-livestatuslistener"></a> 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 a091c31..caf9e1d 100644
--- a/lib/perfdata/influxdbwriter.cpp
+++ b/lib/perfdata/influxdbwriter.cpp
@@ -45,6 +45,7 @@
 #include <boost/foreach.hpp>
 #include <boost/algorithm/string/split.hpp>
 #include <boost/algorithm/string/replace.hpp>
+#include <boost/regex.hpp>
 
 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<Dictionary>(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<unsigned long>(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<unsigned long>(ts);
 
 	Log(LogDebug, "InfluxdbWriter")
 	    << "Add to metric list:'" << msgbuf.str() << "'.";
diff --git a/lib/perfdata/influxdbwriter.hpp b/lib/perfdata/influxdbwriter.hpp
index a241f08..22b8060 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);
 };
 



More information about the icinga-checkins mailing list