]> granicus.if.org Git - icinga2/blob - lib/perfdata/graphitewriter.cpp
649e3d2f0b3056a8e85a56a62015332d73be0ace
[icinga2] / lib / perfdata / graphitewriter.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/)  *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "perfdata/graphitewriter.hpp"
21 #include "perfdata/graphitewriter.tcpp"
22 #include "icinga/service.hpp"
23 #include "icinga/macroprocessor.hpp"
24 #include "icinga/icingaapplication.hpp"
25 #include "icinga/compatutility.hpp"
26 #include "icinga/perfdatavalue.hpp"
27 #include "base/tcpsocket.hpp"
28 #include "base/configtype.hpp"
29 #include "base/objectlock.hpp"
30 #include "base/logger.hpp"
31 #include "base/convert.hpp"
32 #include "base/utility.hpp"
33 #include "base/application.hpp"
34 #include "base/stream.hpp"
35 #include "base/networkstream.hpp"
36 #include "base/exception.hpp"
37 #include "base/statsfunction.hpp"
38 #include <boost/algorithm/string.hpp>
39 #include <boost/algorithm/string/classification.hpp>
40 #include <boost/algorithm/string/split.hpp>
41 #include <boost/algorithm/string/replace.hpp>
42
43 using namespace icinga;
44
45 REGISTER_TYPE(GraphiteWriter);
46
47 REGISTER_STATSFUNCTION(GraphiteWriter, &GraphiteWriter::StatsFunc);
48
49 void GraphiteWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&)
50 {
51         Dictionary::Ptr nodes = new Dictionary();
52
53         for (const GraphiteWriter::Ptr& graphitewriter : ConfigType::GetObjectsByType<GraphiteWriter>()) {
54                 nodes->Set(graphitewriter->GetName(), 1); //add more stats
55         }
56
57         status->Set("graphitewriter", nodes);
58 }
59
60 void GraphiteWriter::Start(bool runtimeCreated)
61 {
62         ObjectImpl<GraphiteWriter>::Start(runtimeCreated);
63
64         Log(LogInformation, "GraphiteWriter")
65             << "'" << GetName() << "' started.";
66
67         m_ReconnectTimer = new Timer();
68         m_ReconnectTimer->SetInterval(10);
69         m_ReconnectTimer->OnTimerExpired.connect(boost::bind(&GraphiteWriter::ReconnectTimerHandler, this));
70         m_ReconnectTimer->Start();
71         m_ReconnectTimer->Reschedule(0);
72
73         Service::OnNewCheckResult.connect(boost::bind(&GraphiteWriter::CheckResultHandler, this, _1, _2));
74 }
75
76 void GraphiteWriter::Stop(bool runtimeRemoved)
77 {
78         Log(LogInformation, "GraphiteWriter")
79             << "'" << GetName() << "' stopped.";
80
81         ObjectImpl<GraphiteWriter>::Stop(runtimeRemoved);
82 }
83
84 void GraphiteWriter::ReconnectTimerHandler(void)
85 {
86         if (m_Stream)
87                 return;
88
89         TcpSocket::Ptr socket = new TcpSocket();
90
91         Log(LogNotice, "GraphiteWriter")
92             << "Reconnecting to Graphite on host '" << GetHost() << "' port '" << GetPort() << "'.";
93
94         try {
95                 socket->Connect(GetHost(), GetPort());
96         } catch (std::exception&) {
97                 Log(LogCritical, "GraphiteWriter")
98                     << "Can't connect to Graphite on host '" << GetHost() << "' port '" << GetPort() << "'.";
99                 return;
100         }
101
102         m_Stream = new NetworkStream(socket);
103 }
104
105 void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
106 {
107         CONTEXT("Processing check result for '" + checkable->GetName() + "'");
108
109         if (!IcingaApplication::GetInstance()->GetEnablePerfdata() || !checkable->GetEnablePerfdata())
110                 return;
111
112         Service::Ptr service = dynamic_pointer_cast<Service>(checkable);
113         Host::Ptr host;
114
115         if (service)
116                 host = service->GetHost();
117         else
118                 host = static_pointer_cast<Host>(checkable);
119
120         MacroProcessor::ResolverList resolvers;
121         if (service)
122                 resolvers.push_back(std::make_pair("service", service));
123         resolvers.push_back(std::make_pair("host", host));
124         resolvers.push_back(std::make_pair("icinga", IcingaApplication::GetInstance()));
125
126         String prefix;
127
128         double ts = cr->GetExecutionEnd();
129
130         /* new mode below. old mode in else tree with 2.4, deprecate it in 2.6, remove in 2.8 TODO */
131         if (!GetEnableLegacyMode()) {
132                 if (service) {
133                         prefix = MacroProcessor::ResolveMacros(GetServiceNameTemplate(), resolvers, cr, NULL, boost::bind(&GraphiteWriter::EscapeMacroMetric, _1, false));
134                 } else {
135                         prefix = MacroProcessor::ResolveMacros(GetHostNameTemplate(), resolvers, cr, NULL, boost::bind(&GraphiteWriter::EscapeMacroMetric, _1, false));
136                 }
137
138                 String prefix_perfdata = prefix + ".perfdata";
139                 String prefix_metadata = prefix + ".metadata";
140
141                 if (GetEnableSendMetadata()) {
142
143                         if (service) {
144                                 SendMetric(prefix_metadata, "state", service->GetState(), ts);
145                         } else {
146                                 SendMetric(prefix_metadata, "state", host->GetState(), ts);
147                         }
148
149                         SendMetric(prefix_metadata, "current_attempt", checkable->GetCheckAttempt(), ts);
150                         SendMetric(prefix_metadata, "max_check_attempts", checkable->GetMaxCheckAttempts(), ts);
151                         SendMetric(prefix_metadata, "state_type", checkable->GetStateType(), ts);
152                         SendMetric(prefix_metadata, "reachable", checkable->IsReachable(), ts);
153                         SendMetric(prefix_metadata, "downtime_depth", checkable->GetDowntimeDepth(), ts);
154                         SendMetric(prefix_metadata, "acknowledgement", checkable->GetAcknowledgement(), ts);
155                         SendMetric(prefix_metadata, "latency", cr->CalculateLatency(), ts);
156                         SendMetric(prefix_metadata, "execution_time", cr->CalculateExecutionTime(), ts);
157                 }
158
159                 SendPerfdata(prefix_perfdata, cr, ts);
160         } else {
161                 if (service) {
162                         prefix = MacroProcessor::ResolveMacros(GetServiceNameTemplate(), resolvers, cr, NULL, boost::bind(&GraphiteWriter::EscapeMacroMetric, _1, true));
163                         SendMetric(prefix, "state", service->GetState(), ts);
164                 } else {
165                         prefix = MacroProcessor::ResolveMacros(GetHostNameTemplate(), resolvers, cr, NULL, boost::bind(&GraphiteWriter::EscapeMacroMetric, _1, true));
166                         SendMetric(prefix, "state", host->GetState(), ts);
167                 }
168
169                 SendMetric(prefix, "current_attempt", checkable->GetCheckAttempt(), ts);
170                 SendMetric(prefix, "max_check_attempts", checkable->GetMaxCheckAttempts(), ts);
171                 SendMetric(prefix, "state_type", checkable->GetStateType(), ts);
172                 SendMetric(prefix, "reachable", checkable->IsReachable(), ts);
173                 SendMetric(prefix, "downtime_depth", checkable->GetDowntimeDepth(), ts);
174                 SendMetric(prefix, "acknowledgement", checkable->GetAcknowledgement(), ts);
175                 SendMetric(prefix, "latency", cr->CalculateLatency(), ts);
176                 SendMetric(prefix, "execution_time", cr->CalculateExecutionTime(), ts);
177                 SendPerfdata(prefix, cr, ts);
178         }
179 }
180
181 void GraphiteWriter::SendPerfdata(const String& prefix, const CheckResult::Ptr& cr, double ts)
182 {
183         Array::Ptr perfdata = cr->GetPerformanceData();
184
185         if (!perfdata)
186                 return;
187
188         ObjectLock olock(perfdata);
189         for (const Value& val : perfdata) {
190                 PerfdataValue::Ptr pdv;
191
192                 if (val.IsObjectType<PerfdataValue>())
193                         pdv = val;
194                 else {
195                         try {
196                                 pdv = PerfdataValue::Parse(val);
197                         } catch (const std::exception&) {
198                                 Log(LogWarning, "GraphiteWriter")
199                                     << "Ignoring invalid perfdata value: " << val;
200                                 continue;
201                         }
202                 }
203
204                 /* new mode below. old mode in else tree with 2.4, deprecate it in 2.6 */
205                 if (!GetEnableLegacyMode()) {
206                         String escaped_key = EscapeMetricLabel(pdv->GetLabel());
207
208                         SendMetric(prefix, escaped_key + ".value", pdv->GetValue(), ts);
209
210                         if (GetEnableSendThresholds()) {
211                                 if (pdv->GetCrit())
212                                         SendMetric(prefix, escaped_key + ".crit", pdv->GetCrit(), ts);
213                                 if (pdv->GetWarn())
214                                         SendMetric(prefix, escaped_key + ".warn", pdv->GetWarn(), ts);
215                                 if (pdv->GetMin())
216                                         SendMetric(prefix, escaped_key + ".min", pdv->GetMin(), ts);
217                                 if (pdv->GetMax())
218                                         SendMetric(prefix, escaped_key + ".max", pdv->GetMax(), ts);
219                         }
220                 } else {
221                         String escaped_key = EscapeMetric(pdv->GetLabel());
222                         boost::algorithm::replace_all(escaped_key, "::", ".");
223                         SendMetric(prefix, escaped_key, pdv->GetValue(), ts);
224
225                         if (pdv->GetCrit())
226                                 SendMetric(prefix, escaped_key + "_crit", pdv->GetCrit(), ts);
227                         if (pdv->GetWarn())
228                                 SendMetric(prefix, escaped_key + "_warn", pdv->GetWarn(), ts);
229                         if (pdv->GetMin())
230                                 SendMetric(prefix, escaped_key + "_min", pdv->GetMin(), ts);
231                         if (pdv->GetMax())
232                                 SendMetric(prefix, escaped_key + "_max", pdv->GetMax(), ts);
233                 }
234         }
235 }
236
237 void GraphiteWriter::SendMetric(const String& prefix, const String& name, double value, double ts)
238 {
239         std::ostringstream msgbuf;
240         msgbuf << prefix << "." << name << " " << Convert::ToString(value) << " " << static_cast<long>(ts);
241
242         Log(LogDebug, "GraphiteWriter")
243             << "Add to metric list:'" << msgbuf.str() << "'.";
244
245         // do not send \n to debug log
246         msgbuf << "\n";
247         String metric = msgbuf.str();
248
249         ObjectLock olock(this);
250
251         if (!m_Stream)
252                 return;
253
254         try {
255                 m_Stream->Write(metric.CStr(), metric.GetLength());
256         } catch (const std::exception& ex) {
257                 Log(LogCritical, "GraphiteWriter")
258                     << "Cannot write to TCP socket on host '" << GetHost() << "' port '" << GetPort() << "'.";
259
260                 m_Stream.reset();
261         }
262 }
263
264 String GraphiteWriter::EscapeMetric(const String& str, bool legacyMode)
265 {
266         String result = str;
267
268         //don't allow '.' in metric prefixes
269         boost::replace_all(result, " ", "_");
270         boost::replace_all(result, ".", "_");
271         boost::replace_all(result, "\\", "_");
272         boost::replace_all(result, "/", "_");
273
274         if (legacyMode)
275                 boost::replace_all(result, "-", "_");
276
277         return result;
278 }
279
280 String GraphiteWriter::EscapeMetricLabel(const String& str)
281 {
282         String result = str;
283
284         //allow to pass '.' in perfdata labels
285         boost::replace_all(result, " ", "_");
286         boost::replace_all(result, "\\", "_");
287         boost::replace_all(result, "/", "_");
288         boost::replace_all(result, "::", ".");
289
290         return result;
291 }
292
293 Value GraphiteWriter::EscapeMacroMetric(const Value& value, bool legacyMode)
294 {
295         if (value.IsObjectType<Array>()) {
296                 Array::Ptr arr = value;
297                 Array::Ptr result = new Array();
298
299                 ObjectLock olock(arr);
300                 for (const Value& arg : arr) {
301                         result->Add(EscapeMetric(arg, legacyMode));
302                 }
303
304                 return Utility::Join(result, '.');
305         } else
306                 return EscapeMetric(value, legacyMode);
307 }
308
309 void GraphiteWriter::ValidateHostNameTemplate(const String& value, const ValidationUtils& utils)
310 {
311         ObjectImpl<GraphiteWriter>::ValidateHostNameTemplate(value, utils);
312
313         if (!MacroProcessor::ValidateMacroString(value))
314                 BOOST_THROW_EXCEPTION(ValidationError(this, boost::assign::list_of("host_name_template"), "Closing $ not found in macro format string '" + value + "'."));
315 }
316
317 void GraphiteWriter::ValidateServiceNameTemplate(const String& value, const ValidationUtils& utils)
318 {
319         ObjectImpl<GraphiteWriter>::ValidateServiceNameTemplate(value, utils);
320
321         if (!MacroProcessor::ValidateMacroString(value))
322                 BOOST_THROW_EXCEPTION(ValidationError(this, boost::assign::list_of("service_name_template"), "Closing $ not found in macro format string '" + value + "'."));
323 }