From bc3411fe8daca44a20d69cf2a82f69b19dd52b86 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Fri, 18 Oct 2013 16:05:56 +0200 Subject: [PATCH] GraphiteWriter: Refactor perfdata parsing. --- components/perfdata/graphitewriter.cpp | 75 ++++++++++++++++++++------ components/perfdata/graphitewriter.h | 3 +- lib/icinga/compatutility.cpp | 9 ++-- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/components/perfdata/graphitewriter.cpp b/components/perfdata/graphitewriter.cpp index 9d990af36..78716bab7 100644 --- a/components/perfdata/graphitewriter.cpp +++ b/components/perfdata/graphitewriter.cpp @@ -32,6 +32,7 @@ #include "base/stream.h" #include "base/networkstream.h" #include "base/bufferedstream.h" +#include #include #include #include @@ -86,7 +87,7 @@ void GraphiteWriter::ReconnectTimerHandler(void) TcpSocket::Ptr socket = boost::make_shared(); - Log(LogInformation, "perfdata", "GraphiteWriter: Reconnect to tcp socket on host '" + GetHost() + "' port '" + GetPort() + "'."); + Log(LogDebug, "perfdata", "GraphiteWriter: Reconnect to tcp socket on host '" + GetHost() + "' port '" + GetPort() + "'."); socket->Connect(GetHost(), GetPort()); NetworkStream::Ptr net_stream = boost::make_shared(socket); @@ -120,6 +121,8 @@ void GraphiteWriter::CheckResultHandler(const Service::Ptr& service, const Dicti String perfdata = CompatUtility::GetCheckResultPerfdata(cr); if (!perfdata.IsEmpty()) { + perfdata.Trim(); + Log(LogDebug, "perfdata", "GraphiteWriter: Processing perfdata: '" + perfdata + "'."); /* @@ -129,34 +132,65 @@ void GraphiteWriter::CheckResultHandler(const Service::Ptr& service, const Dicti std::vector tokens; boost::algorithm::split(tokens, perfdata, boost::is_any_of(" ")); - /* TODO deal with 'foo bar'=0;;; 'baz'=1.0;;; */ + /* TODO deal with white spaces in single quoted labels: 'foo bar'=0;;; 'baz'=1.0;;; + * 1. find first ', find second ' -> if no '=' in between, this is a label + * 2. two single quotes define an escaped single quite + * 3. warn/crit/min/max may be null and semicolon delimiter omitted + * https://www.nagios-plugins.org/doc/guidelines.html#AEN200 + */ BOOST_FOREACH(const String& token, tokens) { + String metricKeyVal = token; + metricKeyVal.Trim(); + std::vector key_val; - boost::algorithm::split(key_val, token, boost::is_any_of("=")); + boost::algorithm::split(key_val, metricKeyVal, boost::is_any_of("=")); if (key_val.size() == 0) { - Log(LogWarning, "perfdata", "GraphiteWriter: Invalid performance data: '" + token + "'."); + Log(LogWarning, "perfdata", "GraphiteWriter: Invalid performance data. No assignment operator found in :'" + metricKeyVal + "'."); return; } String metricName = key_val[0]; + metricName.Trim(); if (key_val.size() == 1) { - Log(LogWarning, "perfdata", "GraphiteWriter: Invalid performance data: '" + token + "'."); + Log(LogWarning, "perfdata", "GraphiteWriter: Invalid performance data: '" + metricKeyVal + "' with key: '" + metricName + "'."); return; } + String metricValues = key_val[1]; + metricValues.Trim(); + std::vector perfdata_values; - boost::algorithm::split(perfdata_values, key_val[1], boost::is_any_of(";")); + boost::algorithm::split(perfdata_values, metricValues, boost::is_any_of(";")); if (perfdata_values.size() == 0) { - Log(LogWarning, "perfdata", "GraphiteWriter: Invalid performance data: '" + token + "'."); + Log(LogWarning, "perfdata", "GraphiteWriter: Invalid performance data: '" + metricKeyVal + + "' with key: '" + metricName + "' and values: '" + metricValues + "'."); return; } - /* TODO remove UOM from value */ String metricValue = perfdata_values[0]; + metricValue.Trim(); + Log(LogDebug, "perfdata", "GraphiteWriter: Trimmed metric value: '" + metricValue + "'."); + + /* extract raw value (digit number digit as double) and uom + * http://en.highscore.de/cpp/boost/stringhandling.html + */ + String metricValueRaw = boost::algorithm::trim_right_copy_if(metricValue, (!boost::algorithm::is_digit() && !boost::algorithm::is_any_of(".,"))); + String metricValueUom = boost::algorithm::trim_left_copy_if(metricValue, (boost::algorithm::is_digit() || boost::algorithm::is_any_of(".,"))); + + Log(LogDebug, "perfdata", "GraphiteWriter: Raw metric value: '" + metricValueRaw + "' with UOM: '" + metricValueUom + "'."); + + /* TODO: Normalize raw value based on UOM + * a. empty - assume a number + * b. 's' - seconds (us, ms) + * c. '%' - percentage + * d. 'B' - bytes (KB, MB, GB, TB) + * e. 'c' - continous counter (snmp) + */ + /* //TODO: Figure out how graphite handles warn/crit/min/max String metricValueWarn, metricValueCrit, metricValueMin, metricValueMax; @@ -170,7 +204,10 @@ void GraphiteWriter::CheckResultHandler(const Service::Ptr& service, const Dicti metricValueMax = perfdata_values[4]; */ - AddServiceMetric(metrics, service, metricName, metricValue); + /* sanitize invalid metric characters */ + SanitizeMetric(metricName); + + AddServiceMetric(metrics, service, metricName, metricValueRaw); } } @@ -182,14 +219,11 @@ void GraphiteWriter::AddServiceMetric(std::vector& metrics, const Servic /* TODO: sanitize host and service names */ String hostName = service->GetHost()->GetName(); String serviceName = service->GetShortName(); + + SanitizeMetric(hostName); + SanitizeMetric(serviceName); + String metricPrefix = hostName + "." + serviceName; - - boost::replace_all(metricPrefix, " ", "_"); - boost::replace_all(metricPrefix, "-", "_"); - boost::replace_all(metricPrefix, ".", "_"); - boost::replace_all(metricPrefix, "\\", "_"); - boost::replace_all(metricPrefix, "/", "_"); - String graphitePrefix = "icinga"; String metric = graphitePrefix + "." + metricPrefix + "." + name + " " + Convert::ToString(value) + " " + Convert::ToString(static_cast(Utility::GetTime())) + "\n"; @@ -224,6 +258,15 @@ void GraphiteWriter::SendMetrics(const std::vector& metrics) } } +void GraphiteWriter::SanitizeMetric(String& str) +{ + boost::replace_all(str, " ", "_"); + boost::replace_all(str, "-", "_"); + boost::replace_all(str, ".", "_"); + boost::replace_all(str, "\\", "_"); + boost::replace_all(str, "/", "_"); +} + void GraphiteWriter::InternalSerialize(const Dictionary::Ptr& bag, int attributeTypes) const { DynamicObject::InternalSerialize(bag, attributeTypes); diff --git a/components/perfdata/graphitewriter.h b/components/perfdata/graphitewriter.h index 0cba76352..cb3162490 100644 --- a/components/perfdata/graphitewriter.h +++ b/components/perfdata/graphitewriter.h @@ -57,8 +57,9 @@ private: Timer::Ptr m_ReconnectTimer; void CheckResultHandler(const Service::Ptr& service, const Dictionary::Ptr& cr); - static void AddServiceMetric(std::vector & metrics, const Service::Ptr& service, const String& name, const Value& value); + static void AddServiceMetric(std::vector& metrics, const Service::Ptr& service, const String& name, const Value& value); void SendMetrics(const std::vector& metrics); + static void SanitizeMetric(String& str); void ReconnectTimerHandler(void); }; diff --git a/lib/icinga/compatutility.cpp b/lib/icinga/compatutility.cpp index a0d0389a9..096e9084d 100644 --- a/lib/icinga/compatutility.cpp +++ b/lib/icinga/compatutility.cpp @@ -430,10 +430,10 @@ Dictionary::Ptr CompatUtility::GetCommandConfigAttributes(const Command::Ptr& co String arg; BOOST_FOREACH(arg, args) { // This is obviously incorrect for non-trivial cases. - commandline = " \"" + CompatUtility::EscapeString(arg) + "\""; + commandline = " \"" + EscapeString(arg) + "\""; } } else if (!commandLine.IsEmpty()) { - commandline = CompatUtility::EscapeString(Convert::ToString(commandLine)); + commandline = EscapeString(Convert::ToString(commandLine)); } else { commandline = ""; } @@ -552,7 +552,10 @@ String CompatUtility::GetCheckResultPerfdata(const Dictionary::Ptr& cr) if (!cr) return Empty; - return EscapeString(cr->Get("performance_data_raw")); + String perfdata = EscapeString(cr->Get("performance_data_raw")); + perfdata.Trim(); + + return perfdata; } String CompatUtility::EscapeString(const String& str)