From: Tobias von der Krone Date: Wed, 28 Jan 2015 09:57:34 +0000 (+0100) Subject: Add OpenTSDB perfdata plugin X-Git-Tag: v2.3.0~328 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=201883ff7069e2b0a3105b5c36d19c84dbaa4968;p=icinga2 Add OpenTSDB perfdata plugin refs #7256 Signed-off-by: Gunnar Beutner --- diff --git a/doc/3-monitoring-basics.md b/doc/3-monitoring-basics.md index eafecb5f6..4df47d8a5 100644 --- a/doc/3-monitoring-basics.md +++ b/doc/3-monitoring-basics.md @@ -2362,6 +2362,69 @@ Currently these events are processed: * State changes * Notifications +### OpenTSDB Writer + +While there are some OpenTSDB collector scripts and daemons like tcollector available for +Icinga 1.x it's more reasonable to directly process the check and plugin performance +in memory in Icinga 2. Once there are new metrics available, Icinga 2 will directly +write them to the defined TSDB TCP socket. + +You can enable the feature using + + # icinga2 feature enable opentsdb + +By default the `OpenTsdbWriter` object expects the TSD to listen at +`127.0.0.1` on port `4242`. + +The current naming schema is + + icinga.host. + icinga.service.. + +for host and service checks. The tag host is always applied. + +To make sure Icinga 2 writes a valid metric into OpenTSDB some characters are replaced +with `_` in the target name: + + \ (and space) + +The resulting name in OpenTSDB might look like: + + www-01 / http-cert / response time + icinga.http_cert.response_time + +In addition to the performance data retrieved from the check plugin, Icinga 2 sends +internal check statistic data to OpenTSDB: + + metric | description + -------------------|------------------------------------------ + current_attempt | current check attempt + max_check_attempts | maximum check attempts until the hard state is reached + reachable | checked object is reachable + downtime_depth | number of downtimes this object is in + execution_time | check execution time + latency | check latency + state | current state of the checked object + state_type | 0=SOFT, 1=HARD state + +While reachable, state and state_type are metrics for the host or service the +other metrics follow the current naming schema + + icinga.check. + +with the following tags + + tag | description + --------|------------------------------------------ + type | the check type, one of [host, service] + host | hostname, the check ran on + service | the service name (if type=service) + +> **Note** +> +> You might want to set the tsd.core.auto_create_metrics setting to `true` +> in your opentsdb.conf configuration file. + ## Status Data diff --git a/doc/5-object-types.md b/doc/5-object-types.md index 7d14d429c..0cff0c405 100644 --- a/doc/5-object-types.md +++ b/doc/5-object-types.md @@ -744,6 +744,27 @@ Example with your custom [global constant](15-language-reference.md#constants) ` host_name_template = GraphiteEnv + ".$host.name$" service_name_template = GraphiteEnv + ".$host.name$.$service.name$" +## OpenTsdbWriter + +Writes check result metrics and performance data to OpenTSDB. + +Example: + + library "perfdata" + + object OpenTsdbWriter "opentsdb" { + host = "127.0.0.1" + port = 4242 + } + +Attributes: + + Name |Description + ----------------------|---------------------- + host |**Optional.** OpenTSDB host address. Defaults to '127.0.0.1'. + port |**Optional.** OpenTSDB port. Defaults to 4242. + + ## GelfWriter Writes event log entries to a defined GELF receiver host (Graylog2, Logstash). diff --git a/etc/icinga2/features-available/opentsdb.conf b/etc/icinga2/features-available/opentsdb.conf new file mode 100644 index 000000000..fcb547d01 --- /dev/null +++ b/etc/icinga2/features-available/opentsdb.conf @@ -0,0 +1,11 @@ +/** + * The OpenTsdbWriter type writes check result metrics and + * performance data to a OpenTSDB tcp socket. + */ + +library "perfdata" + +object OpenTsdbWriter "opentsdb" { + //host = "127.0.0.1" + //port = 4242 +} diff --git a/lib/perfdata/CMakeLists.txt b/lib/perfdata/CMakeLists.txt index ababe1819..ff603f788 100644 --- a/lib/perfdata/CMakeLists.txt +++ b/lib/perfdata/CMakeLists.txt @@ -17,12 +17,13 @@ mkclass_target(gelfwriter.ti gelfwriter.thpp) mkclass_target(graphitewriter.ti graphitewriter.thpp) +mkclass_target(opentsdbwriter.ti opentsdbwriter.thpp) mkclass_target(perfdatawriter.ti perfdatawriter.thpp) mkembedconfig_target(perfdata-type.conf perfdata-type.cpp) set(perfdata_SOURCES - gelfwriter.cpp gelfwriter.thpp graphitewriter.cpp graphitewriter.thpp perfdatawriter.cpp perfdatawriter.thpp perfdata-type.cpp + gelfwriter.cpp gelfwriter.thpp graphitewriter.cpp graphitewriter.thpp opentsdbwriter.cpp opentsdbwriter.thpp perfdatawriter.cpp perfdatawriter.thpp perfdata-type.cpp ) if(ICINGA2_UNITY_BUILD) @@ -49,6 +50,11 @@ install_if_not_exists( ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/features-available ) +install_if_not_exists( + ${PROJECT_SOURCE_DIR}/etc/icinga2/features-available/opentsdb.conf + ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/features-available +) + install_if_not_exists( ${PROJECT_SOURCE_DIR}/etc/icinga2/features-available/perfdata.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/features-available diff --git a/lib/perfdata/opentsdbwriter.cpp b/lib/perfdata/opentsdbwriter.cpp new file mode 100644 index 000000000..543116d4b --- /dev/null +++ b/lib/perfdata/opentsdbwriter.cpp @@ -0,0 +1,250 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "perfdata/opentsdbwriter.hpp" +#include "icinga/service.hpp" +#include "icinga/macroprocessor.hpp" +#include "icinga/icingaapplication.hpp" +#include "icinga/compatutility.hpp" +#include "icinga/perfdatavalue.hpp" +#include "base/tcpsocket.hpp" +#include "base/dynamictype.hpp" +#include "base/objectlock.hpp" +#include "base/logger.hpp" +#include "base/convert.hpp" +#include "base/utility.hpp" +#include "base/application.hpp" +#include "base/stream.hpp" +#include "base/networkstream.hpp" +#include "base/exception.hpp" +#include "base/statsfunction.hpp" +#include +#include +#include +#include +#include + +using namespace icinga; + +REGISTER_TYPE(OpenTsdbWriter); + +REGISTER_STATSFUNCTION(OpenTsdbWriterStats, &OpenTsdbWriter::StatsFunc); + +Value OpenTsdbWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&) +{ + Dictionary::Ptr nodes = new Dictionary(); + + BOOST_FOREACH(const OpenTsdbWriter::Ptr& opentsdbwriter, DynamicType::GetObjectsByType()) { + nodes->Set(opentsdbwriter->GetName(), 1); //add more stats + } + + status->Set("opentsdbwriter", nodes); + + return 0; +} + +void OpenTsdbWriter::Start(void) +{ + DynamicObject::Start(); + + m_ReconnectTimer = new Timer(); + m_ReconnectTimer->SetInterval(10); + m_ReconnectTimer->OnTimerExpired.connect(boost::bind(&OpenTsdbWriter::ReconnectTimerHandler, this)); + m_ReconnectTimer->Start(); + m_ReconnectTimer->Reschedule(0); + + Service::OnNewCheckResult.connect(boost::bind(&OpenTsdbWriter::CheckResultHandler, this, _1, _2)); +} + +void OpenTsdbWriter::ReconnectTimerHandler(void) +{ + if (m_Stream) + return; + + TcpSocket::Ptr socket = new TcpSocket(); + + Log(LogNotice, "OpenTsdbWriter") + << "Reconnect to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() << "'."; + + try { + socket->Connect(GetHost(), GetPort()); + } catch (std::exception&) { + Log(LogCritical, "OpenTsdbWriter") + << "Can't connect to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() << "'."; + return; + } + + m_Stream = new NetworkStream(socket); +} + +void OpenTsdbWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) +{ + CONTEXT("Processing check result for '" + checkable->GetName() + "'"); + + if (!IcingaApplication::GetInstance()->GetEnablePerfdata() || !checkable->GetEnablePerfdata()) + return; + + Service::Ptr service = dynamic_pointer_cast(checkable); + Host::Ptr host; + + if (service) + host = service->GetHost(); + else + host = static_pointer_cast(checkable); + + String hostName = host->GetName(); + + String metric; + std::map tags; + tags["host"] = hostName; + + if (service) { + String serviceName = service->GetShortName(); + EscapeMetric(serviceName); + metric = "icinga.service." + serviceName; + + SendMetric(metric + ".state", tags, service->GetState()); + } else { + metric = "icinga.host"; + SendMetric(metric + ".state", tags, host->GetState()); + } + + SendMetric(metric + ".state_type", tags, checkable->GetStateType()); + SendMetric(metric + ".reachable", tags, checkable->IsReachable()); + SendMetric(metric + ".downtime_depth", tags, checkable->GetDowntimeDepth()); + + SendPerfdata(metric, tags, cr); + + metric = "icinga.check"; + + if (service) { + tags["type"] = "service"; + String serviceName = service->GetShortName(); + EscapeTag(serviceName); + tags["service"] = serviceName; + } else { + tags["type"] = "host"; + } + + SendMetric(metric + ".current_attempt", tags, checkable->GetCheckAttempt()); + SendMetric(metric + ".max_check_attempts", tags, checkable->GetMaxCheckAttempts()); + SendMetric(metric + ".latency", tags, Service::CalculateLatency(cr)); + SendMetric(metric + ".execution_time", tags, Service::CalculateExecutionTime(cr)); +} + +void OpenTsdbWriter::SendPerfdata(const String& metric, const std::map& tags, const CheckResult::Ptr& cr) +{ + Array::Ptr perfdata = cr->GetPerformanceData(); + + if (!perfdata) + return; + + ObjectLock olock(perfdata); + BOOST_FOREACH(const Value& val, perfdata) { + PerfdataValue::Ptr pdv; + + if (val.IsObjectType()) + pdv = val; + else { + try { + pdv = PerfdataValue::Parse(val); + } catch (const std::exception&) { + Log(LogWarning, "OpenTsdbWriter") + << "Ignoring invalid perfdata value: " << val; + continue; + } + } + + String escaped_key = pdv->GetLabel(); + EscapeMetric(escaped_key); + boost::algorithm::replace_all(escaped_key, "::", "."); + + SendMetric(metric + "." + escaped_key, tags, pdv->GetValue()); + + if (pdv->GetCrit()) + SendMetric(metric + "." + escaped_key + "_crit", tags, pdv->GetCrit()); + if (pdv->GetWarn()) + SendMetric(metric + "." + escaped_key + "_warn", tags, pdv->GetWarn()); + if (pdv->GetMin()) + SendMetric(metric + "." + escaped_key + "_min", tags, pdv->GetMin()); + if (pdv->GetMax()) + SendMetric(metric + "." + escaped_key + "_max", tags, pdv->GetMax()); + } +} + +void OpenTsdbWriter::SendMetric(const String& metric, const std::map& tags, double value) +{ + String tags_string = ""; + BOOST_FOREACH(const Dictionary::Pair& tag, tags) { + tags_string += " " + tag.first + "=" + Convert::ToString(tag.second); + } + + std::ostringstream msgbuf; + /* + * must be (http://opentsdb.net/docs/build/html/user_guide/writing.html) + * put + * "tags" must include at least one tag, we use "host=HOSTNAME" + */ + msgbuf << "put " << metric << " " << static_cast(Utility::GetTime()) << " " << Convert::ToString(value) << " " << tags_string; + + Log(LogDebug, "OpenTsdbWriter") + << "Add to metric list:'" << msgbuf.str() << "'."; + + /* do not send \n to debug log */ + msgbuf << "\n"; + String put = msgbuf.str(); + + ObjectLock olock(this); + + if (!m_Stream) + return; + + try { + m_Stream->Write(put.CStr(), put.GetLength()); + } catch (const std::exception& ex) { + Log(LogCritical, "OpenTsdbWriter") + << "Cannot write to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() + "'."; + + m_Stream.reset(); + } +} + +/* for metric and tag name rules, see + * http://opentsdb.net/docs/build/html/user_guide/writing.html#metrics-and-tags + */ +String OpenTsdbWriter::EscapeTag(const String& str) +{ + String result = str; + + boost::replace_all(result, " ", "_"); + boost::replace_all(result, "\\", "_"); + + return result; +} + +String OpenTsdbWriter::EscapeMetric(const String& str) +{ + String result = str; + + boost::replace_all(result, " ", "_"); + boost::replace_all(result, ".", "_"); + boost::replace_all(result, "\\", "_"); + + return result; +} diff --git a/lib/perfdata/opentsdbwriter.hpp b/lib/perfdata/opentsdbwriter.hpp new file mode 100644 index 000000000..1f6ada102 --- /dev/null +++ b/lib/perfdata/opentsdbwriter.hpp @@ -0,0 +1,65 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef OPENTSDBWRITER_H +#define OPENTSDBWRITER_H + +#include "perfdata/opentsdbwriter.thpp" +#include "icinga/service.hpp" +#include "base/dynamicobject.hpp" +#include "base/tcpsocket.hpp" +#include "base/timer.hpp" +#include + +namespace icinga +{ + +/** + * An Icinga opentsdb writer. + * + * @ingroup perfdata + */ +class OpenTsdbWriter : public ObjectImpl +{ +public: + DECLARE_OBJECT(OpenTsdbWriter); + DECLARE_OBJECTNAME(OpenTsdbWriter); + + static Value StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata); + +protected: + virtual void Start(void); + +private: + Stream::Ptr m_Stream; + + Timer::Ptr m_ReconnectTimer; + + void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); + void SendMetric(const String& metric, const std::map& tags, double value); + void SendPerfdata(const String& metric, const std::map& tags, const CheckResult::Ptr& cr); + static String EscapeTag(const String& str); + static String EscapeMetric(const String& str); + + void ReconnectTimerHandler(void); +}; + +} + +#endif /* OPENTSDBWRITER_H */ diff --git a/lib/perfdata/opentsdbwriter.ti b/lib/perfdata/opentsdbwriter.ti new file mode 100644 index 000000000..4a21f2943 --- /dev/null +++ b/lib/perfdata/opentsdbwriter.ti @@ -0,0 +1,16 @@ +#include "base/dynamicobject.hpp" + +namespace icinga +{ + +class OpenTsdbWriter : DynamicObject +{ + [config] String host { + default {{{ return "127.0.0.1"; }}} + }; + [config] String port { + default {{{ return "4242"; }}} + }; +}; + +} diff --git a/lib/perfdata/perfdata-type.conf b/lib/perfdata/perfdata-type.conf index 99245b253..42938b4f1 100644 --- a/lib/perfdata/perfdata-type.conf +++ b/lib/perfdata/perfdata-type.conf @@ -40,3 +40,8 @@ %attribute %string "source" } +%type OpenTsdbWriter { + %attribute %string "host", + %attribute %string "port", +} +