2013-10-14 20:12:42 +02:00
/******************************************************************************
* Icinga 2 *
2017-01-10 15:54:22 +01:00
* Copyright ( C ) 2012 - 2017 Icinga Development Team ( https : //www.icinga.com/) *
2013-10-14 20:12:42 +02:00
* *
* 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 . *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2014-05-25 16:23:35 +02:00
# include "perfdata/graphitewriter.hpp"
2015-03-28 11:04:42 +01:00
# include "perfdata/graphitewriter.tcpp"
2014-05-25 16:23:35 +02:00
# include "icinga/service.hpp"
# include "icinga/macroprocessor.hpp"
# include "icinga/icingaapplication.hpp"
# include "base/tcpsocket.hpp"
2015-08-15 20:28:05 +02:00
# include "base/configtype.hpp"
2014-05-25 16:23:35 +02:00
# include "base/objectlock.hpp"
2014-10-19 14:21:12 +02:00
# include "base/logger.hpp"
2014-05-25 16:23:35 +02:00
# include "base/convert.hpp"
# include "base/utility.hpp"
2017-05-15 15:51:39 +02:00
# include "base/perfdatavalue.hpp"
2014-05-25 16:23:35 +02:00
# include "base/application.hpp"
# include "base/stream.hpp"
# include "base/networkstream.hpp"
# include "base/exception.hpp"
# include "base/statsfunction.hpp"
2013-10-18 16:05:56 +02:00
# include <boost/algorithm/string.hpp>
2013-10-14 20:12:42 +02:00
# include <boost/algorithm/string/classification.hpp>
# include <boost/algorithm/string/split.hpp>
2013-10-15 20:37:58 +02:00
# include <boost/algorithm/string/replace.hpp>
2013-10-14 20:12:42 +02:00
using namespace icinga ;
REGISTER_TYPE ( GraphiteWriter ) ;
2015-09-21 11:44:58 +02:00
REGISTER_STATSFUNCTION ( GraphiteWriter , & GraphiteWriter : : StatsFunc ) ;
2014-02-17 16:34:18 +01:00
2017-05-15 17:35:36 +02:00
GraphiteWriter : : GraphiteWriter ( void )
: m_WorkQueue ( 10000000 , 1 )
{ }
void GraphiteWriter : : OnConfigLoaded ( void )
{
ObjectImpl < GraphiteWriter > : : OnConfigLoaded ( ) ;
m_WorkQueue . SetName ( " GraphiteWriter, " + GetName ( ) ) ;
}
void GraphiteWriter : : StatsFunc ( const Dictionary : : Ptr & status , const Array : : Ptr & perfdata )
2014-02-17 16:34:18 +01:00
{
2014-11-08 21:17:16 +01:00
Dictionary : : Ptr nodes = new Dictionary ( ) ;
2014-02-18 10:53:44 +01:00
2016-08-25 06:19:44 +02:00
for ( const GraphiteWriter : : Ptr & graphitewriter : ConfigType : : GetObjectsByType < GraphiteWriter > ( ) ) {
2017-05-15 17:35:36 +02:00
size_t workQueueItems = graphitewriter - > m_WorkQueue . GetLength ( ) ;
double workQueueItemRate = graphitewriter - > m_WorkQueue . GetTaskCount ( 60 ) / 60.0 ;
Dictionary : : Ptr stats = new Dictionary ( ) ;
stats - > Set ( " work_queue_items " , workQueueItems ) ;
stats - > Set ( " work_queue_item_rate " , workQueueItemRate ) ;
2017-06-06 19:50:37 +02:00
stats - > Set ( " connected " , graphitewriter - > GetConnected ( ) ) ;
2017-05-15 17:35:36 +02:00
nodes - > Set ( graphitewriter - > GetName ( ) , stats ) ;
perfdata - > Add ( new PerfdataValue ( " graphitewriter_ " + graphitewriter - > GetName ( ) + " _work_queue_items " , workQueueItems ) ) ;
perfdata - > Add ( new PerfdataValue ( " graphitewriter_ " + graphitewriter - > GetName ( ) + " _work_queue_item_rate " , workQueueItemRate ) ) ;
2014-02-18 10:53:44 +01:00
}
status - > Set ( " graphitewriter " , nodes ) ;
2014-02-17 16:34:18 +01:00
}
2015-08-20 17:18:48 +02:00
void GraphiteWriter : : Start ( bool runtimeCreated )
2013-10-14 20:12:42 +02:00
{
2015-08-20 17:18:48 +02:00
ObjectImpl < GraphiteWriter > : : Start ( runtimeCreated ) ;
2013-10-14 20:12:42 +02:00
2017-02-08 14:53:52 +01:00
Log ( LogInformation , " GraphiteWriter " )
< < " ' " < < GetName ( ) < < " ' started. " ;
2017-05-15 17:35:36 +02:00
/* Register exception handler for WQ tasks. */
m_WorkQueue . SetExceptionCallback ( boost : : bind ( & GraphiteWriter : : ExceptionHandler , this , _1 ) ) ;
/* Timer for reconnecting */
2014-11-08 21:17:16 +01:00
m_ReconnectTimer = new Timer ( ) ;
2013-10-14 20:12:42 +02:00
m_ReconnectTimer - > SetInterval ( 10 ) ;
m_ReconnectTimer - > OnTimerExpired . connect ( boost : : bind ( & GraphiteWriter : : ReconnectTimerHandler , this ) ) ;
m_ReconnectTimer - > Start ( ) ;
m_ReconnectTimer - > Reschedule ( 0 ) ;
2017-06-06 19:50:37 +02:00
/* Register event handlers. */
Checkable : : OnNewCheckResult . connect ( boost : : bind ( & GraphiteWriter : : CheckResultHandler , this , _1 , _2 ) ) ;
2013-10-14 20:12:42 +02:00
}
2017-02-08 14:53:52 +01:00
void GraphiteWriter : : Stop ( bool runtimeRemoved )
{
Log ( LogInformation , " GraphiteWriter " )
< < " ' " < < GetName ( ) < < " ' stopped. " ;
2017-05-15 17:35:36 +02:00
m_WorkQueue . Join ( ) ;
2017-02-08 14:53:52 +01:00
ObjectImpl < GraphiteWriter > : : Stop ( runtimeRemoved ) ;
}
2017-05-15 17:35:36 +02:00
void GraphiteWriter : : AssertOnWorkQueue ( void )
2013-10-14 20:12:42 +02:00
{
2017-05-15 17:35:36 +02:00
ASSERT ( m_WorkQueue . IsWorkerThread ( ) ) ;
}
void GraphiteWriter : : ExceptionHandler ( boost : : exception_ptr exp )
{
Log ( LogCritical , " GraphiteWriter " , " Exception during Graphite operation: Verify that your backend is operational! " ) ;
Log ( LogDebug , " GraphiteWriter " )
< < " Exception during Graphite operation: " < < DiagnosticInformation ( exp ) ;
if ( GetConnected ( ) ) {
m_Stream - > Close ( ) ;
SetConnected ( false ) ;
}
}
void GraphiteWriter : : Reconnect ( void )
{
AssertOnWorkQueue ( ) ;
double startTime = Utility : : GetTime ( ) ;
CONTEXT ( " Reconnecting to Graphite ' " + GetName ( ) + " ' " ) ;
SetShouldConnect ( true ) ;
if ( GetConnected ( ) )
2014-08-06 09:59:38 +02:00
return ;
2013-10-14 20:12:42 +02:00
2014-11-08 21:17:16 +01:00
TcpSocket : : Ptr socket = new TcpSocket ( ) ;
2013-10-14 20:12:42 +02:00
2014-10-20 10:09:57 +02:00
Log ( LogNotice , " GraphiteWriter " )
< < " Reconnecting to Graphite on host ' " < < GetHost ( ) < < " ' port ' " < < GetPort ( ) < < " '. " ;
2014-06-05 16:17:53 +02:00
try {
socket - > Connect ( GetHost ( ) , GetPort ( ) ) ;
2017-05-15 17:35:36 +02:00
} catch ( const std : : exception & ex ) {
2014-10-20 10:09:57 +02:00
Log ( LogCritical , " GraphiteWriter " )
< < " Can't connect to Graphite on host ' " < < GetHost ( ) < < " ' port ' " < < GetPort ( ) < < " '. " ;
2017-05-15 17:35:36 +02:00
throw ex ;
2014-06-05 16:17:53 +02:00
}
2013-10-14 20:12:42 +02:00
2014-11-08 21:17:16 +01:00
m_Stream = new NetworkStream ( socket ) ;
2017-05-15 17:35:36 +02:00
SetConnected ( true ) ;
Log ( LogInformation , " GraphiteWriter " )
< < " Finished reconnecting to Graphite in " < < std : : setw ( 2 ) < < Utility : : GetTime ( ) - startTime < < " second(s). " ;
}
void GraphiteWriter : : ReconnectTimerHandler ( void )
{
m_WorkQueue . Enqueue ( boost : : bind ( & GraphiteWriter : : Reconnect , this ) , PriorityNormal ) ;
}
void GraphiteWriter : : Disconnect ( void )
{
AssertOnWorkQueue ( ) ;
if ( ! GetConnected ( ) )
return ;
m_Stream - > Close ( ) ;
SetConnected ( false ) ;
2013-10-14 20:12:42 +02:00
}
2014-04-03 15:36:13 +02:00
void GraphiteWriter : : CheckResultHandler ( const Checkable : : Ptr & checkable , const CheckResult : : Ptr & cr )
2013-10-14 20:12:42 +02:00
{
2017-06-06 19:50:37 +02:00
m_WorkQueue . Enqueue ( boost : : bind ( & GraphiteWriter : : CheckResultHandlerInternal , this , checkable , cr ) ) ;
2017-05-15 17:35:36 +02:00
}
2017-06-06 19:50:37 +02:00
void GraphiteWriter : : CheckResultHandlerInternal ( const Checkable : : Ptr & checkable , const CheckResult : : Ptr & cr )
2017-05-15 17:35:36 +02:00
{
AssertOnWorkQueue ( ) ;
2014-08-14 17:08:11 +02:00
CONTEXT ( " Processing check result for ' " + checkable - > GetName ( ) + " ' " ) ;
2014-04-03 15:36:13 +02:00
if ( ! IcingaApplication : : GetInstance ( ) - > GetEnablePerfdata ( ) | | ! checkable - > GetEnablePerfdata ( ) )
2013-10-14 20:12:42 +02:00
return ;
2014-04-03 15:36:13 +02:00
Host : : Ptr host ;
2017-08-09 18:52:35 +02:00
Service : : Ptr service ;
boost : : tie ( host , service ) = GetHostService ( checkable ) ;
2013-11-08 15:42:46 +01:00
2014-10-11 20:38:24 +02:00
MacroProcessor : : ResolverList resolvers ;
if ( service )
resolvers . push_back ( std : : make_pair ( " service " , service ) ) ;
resolvers . push_back ( std : : make_pair ( " host " , host ) ) ;
resolvers . push_back ( std : : make_pair ( " icinga " , IcingaApplication : : GetInstance ( ) ) ) ;
2014-10-12 13:30:39 +02:00
String prefix ;
2014-10-11 20:38:24 +02:00
2017-08-09 18:52:35 +02:00
if ( service ) {
prefix = MacroProcessor : : ResolveMacros ( GetServiceNameTemplate ( ) , resolvers , cr , NULL , boost : : bind ( & GraphiteWriter : : EscapeMacroMetric , _1 ) ) ;
} else {
prefix = MacroProcessor : : ResolveMacros ( GetHostNameTemplate ( ) , resolvers , cr , NULL , boost : : bind ( & GraphiteWriter : : EscapeMacroMetric , _1 ) ) ;
}
2015-06-19 18:11:55 +02:00
2017-08-09 18:52:35 +02:00
String prefixPerfdata = prefix + " .perfdata " ;
String prefixMetadata = prefix + " .metadata " ;
2015-06-19 18:11:55 +02:00
2017-08-09 18:52:35 +02:00
double ts = cr - > GetExecutionEnd ( ) ;
2014-04-01 20:30:44 +02:00
2017-08-09 18:52:35 +02:00
if ( GetEnableSendMetadata ( ) ) {
2015-06-19 18:11:55 +02:00
if ( service ) {
2017-08-09 18:52:35 +02:00
SendMetric ( prefixMetadata , " state " , service - > GetState ( ) , ts ) ;
2015-06-19 18:11:55 +02:00
} else {
2017-08-09 18:52:35 +02:00
SendMetric ( prefixMetadata , " state " , host - > GetState ( ) , ts ) ;
2015-06-19 18:11:55 +02:00
}
2014-04-01 20:30:44 +02:00
2017-08-09 18:52:35 +02:00
SendMetric ( prefixMetadata , " current_attempt " , checkable - > GetCheckAttempt ( ) , ts ) ;
SendMetric ( prefixMetadata , " max_check_attempts " , checkable - > GetMaxCheckAttempts ( ) , ts ) ;
SendMetric ( prefixMetadata , " state_type " , checkable - > GetStateType ( ) , ts ) ;
SendMetric ( prefixMetadata , " reachable " , checkable - > IsReachable ( ) , ts ) ;
SendMetric ( prefixMetadata , " downtime_depth " , checkable - > GetDowntimeDepth ( ) , ts ) ;
SendMetric ( prefixMetadata , " acknowledgement " , checkable - > GetAcknowledgement ( ) , ts ) ;
SendMetric ( prefixMetadata , " latency " , cr - > CalculateLatency ( ) , ts ) ;
SendMetric ( prefixMetadata , " execution_time " , cr - > CalculateExecutionTime ( ) , ts ) ;
2014-04-01 20:30:44 +02:00
}
2017-08-09 18:52:35 +02:00
SendPerfdata ( prefixPerfdata , cr , ts ) ;
2014-04-01 20:30:44 +02:00
}
2015-02-06 08:51:33 +01:00
void GraphiteWriter : : SendPerfdata ( const String & prefix , const CheckResult : : Ptr & cr , double ts )
2014-04-01 20:30:44 +02:00
{
2014-09-17 15:38:39 +02:00
Array : : Ptr perfdata = cr - > GetPerformanceData ( ) ;
2013-10-14 20:12:42 +02:00
2014-09-17 16:35:42 +02:00
if ( ! perfdata )
return ;
2013-11-30 17:42:50 +01:00
ObjectLock olock ( perfdata ) ;
2016-08-25 06:19:44 +02:00
for ( const Value & val : perfdata ) {
2014-09-17 15:38:39 +02:00
PerfdataValue : : Ptr pdv ;
2015-02-11 13:12:08 +01:00
2014-09-17 15:38:39 +02:00
if ( val . IsObjectType < PerfdataValue > ( ) )
pdv = val ;
else {
try {
pdv = PerfdataValue : : Parse ( val ) ;
} catch ( const std : : exception & ) {
2014-10-20 10:09:57 +02:00
Log ( LogWarning , " GraphiteWriter " )
< < " Ignoring invalid perfdata value: " < < val ;
2014-09-17 15:38:39 +02:00
continue ;
}
}
2015-02-11 13:12:08 +01:00
2017-08-09 18:52:35 +02:00
String escapedKey = EscapeMetricLabel ( pdv - > GetLabel ( ) ) ;
2015-06-19 18:11:55 +02:00
2017-08-09 18:52:35 +02:00
SendMetric ( prefix , escapedKey + " .value " , pdv - > GetValue ( ) , ts ) ;
if ( GetEnableSendThresholds ( ) ) {
2015-06-19 18:11:55 +02:00
if ( pdv - > GetCrit ( ) )
2017-08-09 18:52:35 +02:00
SendMetric ( prefix , escapedKey + " .crit " , pdv - > GetCrit ( ) , ts ) ;
2015-06-19 18:11:55 +02:00
if ( pdv - > GetWarn ( ) )
2017-08-09 18:52:35 +02:00
SendMetric ( prefix , escapedKey + " .warn " , pdv - > GetWarn ( ) , ts ) ;
2015-06-19 18:11:55 +02:00
if ( pdv - > GetMin ( ) )
2017-08-09 18:52:35 +02:00
SendMetric ( prefix , escapedKey + " .min " , pdv - > GetMin ( ) , ts ) ;
2015-06-19 18:11:55 +02:00
if ( pdv - > GetMax ( ) )
2017-08-09 18:52:35 +02:00
SendMetric ( prefix , escapedKey + " .max " , pdv - > GetMax ( ) , ts ) ;
2015-06-19 18:11:55 +02:00
}
2013-11-07 14:38:37 +01:00
}
2013-10-14 20:12:42 +02:00
}
2015-02-06 08:51:33 +01:00
void GraphiteWriter : : SendMetric ( const String & prefix , const String & name , double value , double ts )
2013-10-14 20:12:42 +02:00
{
2013-11-08 15:42:46 +01:00
std : : ostringstream msgbuf ;
2015-02-06 08:51:33 +01:00
msgbuf < < prefix < < " . " < < name < < " " < < Convert : : ToString ( value ) < < " " < < static_cast < long > ( ts ) ;
2013-10-14 20:12:42 +02:00
2014-10-19 17:52:17 +02:00
Log ( LogDebug , " GraphiteWriter " )
< < " Add to metric list:' " < < msgbuf . str ( ) < < " '. " ;
2014-08-05 00:53:18 +02:00
// do not send \n to debug log
msgbuf < < " \n " ;
2013-11-08 15:42:46 +01:00
String metric = msgbuf . str ( ) ;
2013-10-14 20:12:42 +02:00
2013-11-07 14:38:37 +01:00
ObjectLock olock ( this ) ;
2013-10-14 20:12:42 +02:00
2017-06-06 19:50:37 +02:00
if ( ! GetConnected ( ) )
2013-11-07 14:38:37 +01:00
return ;
2013-10-18 09:11:21 +02:00
2013-11-07 14:38:37 +01:00
try {
m_Stream - > Write ( metric . CStr ( ) , metric . GetLength ( ) ) ;
} catch ( const std : : exception & ex ) {
2014-10-19 17:52:17 +02:00
Log ( LogCritical , " GraphiteWriter " )
< < " Cannot write to TCP socket on host ' " < < GetHost ( ) < < " ' port ' " < < GetPort ( ) < < " '. " ;
2013-10-18 09:11:21 +02:00
2017-06-06 19:50:37 +02:00
throw ex ;
2013-10-14 20:12:42 +02:00
}
}
2017-08-09 18:52:35 +02:00
String GraphiteWriter : : EscapeMetric ( const String & str )
2013-10-18 16:05:56 +02:00
{
2014-10-12 13:30:39 +02:00
String result = str ;
2015-06-19 18:11:55 +02:00
//don't allow '.' in metric prefixes
2014-10-12 13:30:39 +02:00
boost : : replace_all ( result , " " , " _ " ) ;
boost : : replace_all ( result , " . " , " _ " ) ;
boost : : replace_all ( result , " \\ " , " _ " ) ;
boost : : replace_all ( result , " / " , " _ " ) ;
2015-06-19 18:11:55 +02:00
return result ;
}
String GraphiteWriter : : EscapeMetricLabel ( const String & str )
{
String result = str ;
//allow to pass '.' in perfdata labels
boost : : replace_all ( result , " " , " _ " ) ;
boost : : replace_all ( result , " \\ " , " _ " ) ;
boost : : replace_all ( result , " / " , " _ " ) ;
boost : : replace_all ( result , " :: " , " . " ) ;
2014-10-12 13:30:39 +02:00
return result ;
2013-10-18 16:05:56 +02:00
}
2014-11-26 20:43:42 +01:00
2017-08-09 18:52:35 +02:00
Value GraphiteWriter : : EscapeMacroMetric ( const Value & value )
2014-11-26 20:43:42 +01:00
{
if ( value . IsObjectType < Array > ( ) ) {
Array : : Ptr arr = value ;
Array : : Ptr result = new Array ( ) ;
ObjectLock olock ( arr ) ;
2016-08-25 06:19:44 +02:00
for ( const Value & arg : arr ) {
2017-08-09 18:52:35 +02:00
result - > Add ( EscapeMetric ( arg ) ) ;
2014-11-26 20:43:42 +01:00
}
return Utility : : Join ( result , ' . ' ) ;
} else
2017-08-09 18:52:35 +02:00
return EscapeMetric ( value ) ;
2014-11-26 20:43:42 +01:00
}
2015-02-11 13:12:08 +01:00
2014-11-30 23:32:13 +01:00
void GraphiteWriter : : ValidateHostNameTemplate ( const String & value , const ValidationUtils & utils )
2015-02-11 13:12:08 +01:00
{
2014-11-30 23:32:13 +01:00
ObjectImpl < GraphiteWriter > : : ValidateHostNameTemplate ( value , utils ) ;
2015-02-11 15:47:45 +01:00
2014-11-30 23:32:13 +01:00
if ( ! MacroProcessor : : ValidateMacroString ( value ) )
BOOST_THROW_EXCEPTION ( ValidationError ( this , boost : : assign : : list_of ( " host_name_template " ) , " Closing $ not found in macro format string ' " + value + " '. " ) ) ;
}
void GraphiteWriter : : ValidateServiceNameTemplate ( const String & value , const ValidationUtils & utils )
{
ObjectImpl < GraphiteWriter > : : ValidateServiceNameTemplate ( value , utils ) ;
if ( ! MacroProcessor : : ValidateMacroString ( value ) )
BOOST_THROW_EXCEPTION ( ValidationError ( this , boost : : assign : : list_of ( " service_name_template " ) , " Closing $ not found in macro format string ' " + value + " '. " ) ) ;
2015-02-11 13:12:08 +01:00
}