TrinityCore
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Metric.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "Metric.h"
19#include "Common.h"
20#include "Config.h"
21#include "DeadlineTimer.h"
22#include "Log.h"
23#include "Strand.h"
24#include "Util.h"
25#include <boost/algorithm/string/replace.hpp>
26#include <boost/asio/ip/tcp.hpp>
27
28void Metric::Initialize(std::string const& realmName, Trinity::Asio::IoContext& ioContext, std::function<void()> overallStatusLogger)
29{
30 _dataStream = std::make_unique<boost::asio::ip::tcp::iostream>();
32 _batchTimer = std::make_unique<Trinity::Asio::DeadlineTimer>(ioContext);
33 _overallStatusTimer = std::make_unique<Trinity::Asio::DeadlineTimer>(ioContext);
34 _overallStatusLogger = overallStatusLogger;
36}
37
39{
40 auto& stream = static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream());
41 stream.connect(_hostname, _port);
42 auto error = stream.error();
43 if (error)
44 {
45 TC_LOG_ERROR("metric", "Error connecting to '{}:{}', disabling Metric. Error message : {}",
46 _hostname, _port, error.message());
47 _enabled = false;
48 return false;
49 }
50 stream.clear();
51 return true;
52}
53
55{
56 bool previousValue = _enabled;
57 _enabled = sConfigMgr->GetBoolDefault("Metric.Enable", false);
58 _updateInterval = sConfigMgr->GetIntDefault("Metric.Interval", 1);
59 if (_updateInterval < 1)
60 {
61 TC_LOG_ERROR("metric", "'Metric.Interval' config set to {}, overriding to 1.", _updateInterval);
63 }
64
65 _overallStatusTimerInterval = sConfigMgr->GetIntDefault("Metric.OverallStatusInterval", 1);
67 {
68 TC_LOG_ERROR("metric", "'Metric.OverallStatusInterval' config set to {}, overriding to 1.", _overallStatusTimerInterval);
70 }
71
72 _thresholds.clear();
73 std::vector<std::string> thresholdSettings = sConfigMgr->GetKeysByString("Metric.Threshold.");
74 for (std::string const& thresholdSetting : thresholdSettings)
75 {
76 int thresholdValue = sConfigMgr->GetIntDefault(thresholdSetting, 0);
77 std::string thresholdName = thresholdSetting.substr(strlen("Metric.Threshold."));
78 _thresholds[thresholdName] = thresholdValue;
79 }
80
81 // Schedule a send at this point only if the config changed from Disabled to Enabled.
82 // Cancel any scheduled operation if the config changed from Enabled to Disabled.
83 if (_enabled && !previousValue)
84 {
85 std::string connectionInfo = sConfigMgr->GetStringDefault("Metric.ConnectionInfo", "");
86 if (connectionInfo.empty())
87 {
88 TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' not specified in configuration file.");
89 return;
90 }
91
92 std::vector<std::string_view> tokens = Trinity::Tokenize(connectionInfo, ';', true);
93 if (tokens.size() != 3)
94 {
95 TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' specified with wrong format in configuration file.");
96 return;
97 }
98
99 _hostname.assign(tokens[0]);
100 _port.assign(tokens[1]);
101 _databaseName.assign(tokens[2]);
102 Connect();
103
104 ScheduleSend();
106 }
107}
108
110{
112 {
115 }
116}
117
118bool Metric::ShouldLog(std::string const& category, int64 value) const
119{
120 auto threshold = _thresholds.find(category);
121 if (threshold == _thresholds.end())
122 return false;
123 return value >= threshold->second;
124}
125
126void Metric::LogEvent(std::string category, std::string title, std::string description)
127{
128 using namespace std::chrono;
129
130 MetricData* data = new MetricData;
131 data->Category = std::move(category);
132 data->Timestamp = system_clock::now();
133 data->Type = METRIC_DATA_EVENT;
134 data->Title = std::move(title);
135 data->ValueOrEventText = std::move(description);
136
137 _queuedData.Enqueue(data);
138}
139
141{
142 using namespace std::chrono;
143
144 std::stringstream batchedData;
145 MetricData* data;
146 bool firstLoop = true;
147 while (_queuedData.Dequeue(data))
148 {
149 if (!firstLoop)
150 batchedData << "\n";
151
152 batchedData << data->Category;
153 if (!_realmName.empty())
154 batchedData << ",realm=" << _realmName;
155
156 for (MetricTag const& tag : data->Tags)
157 batchedData << "," << tag.first << "=" << FormatInfluxDBTagValue(tag.second);
158
159 batchedData << " ";
160
161 switch (data->Type)
162 {
164 batchedData << "value=" << data->ValueOrEventText;
165 break;
167 batchedData << "title=\"" << data->Title << "\",text=\"" << data->ValueOrEventText << "\"";
168 break;
169 }
170
171 batchedData << " ";
172
173 batchedData << std::to_string(duration_cast<nanoseconds>(data->Timestamp.time_since_epoch()).count());
174
175 firstLoop = false;
176 delete data;
177 }
178
179 // Check if there's any data to send
180 if (batchedData.tellp() == std::streampos(0))
181 {
182 ScheduleSend();
183 return;
184 }
185
186 if (!GetDataStream().good() && !Connect())
187 return;
188
189 GetDataStream() << "POST " << "/write?db=" << _databaseName << " HTTP/1.1\r\n";
190 GetDataStream() << "Host: " << _hostname << ":" << _port << "\r\n";
191 GetDataStream() << "Accept: */*\r\n";
192 GetDataStream() << "Content-Type: application/octet-stream\r\n";
193 GetDataStream() << "Content-Transfer-Encoding: binary\r\n";
194
195 GetDataStream() << "Content-Length: " << std::to_string(batchedData.tellp()) << "\r\n\r\n";
196 GetDataStream() << batchedData.rdbuf();
197
198 std::string http_version;
199 GetDataStream() >> http_version;
200 unsigned int status_code = 0;
201 GetDataStream() >> status_code;
202 if (status_code != 204)
203 {
204 TC_LOG_ERROR("metric", "Error sending data, returned HTTP code: {}", status_code);
205 }
206
207 // Read and ignore the status description
208 std::string status_description;
209 std::getline(GetDataStream(), status_description);
210 // Read headers
211 std::string header;
212 while (std::getline(GetDataStream(), header) && header != "\r")
213 if (header == "Connection: close\r")
214 static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream()).close();
215
216 ScheduleSend();
217}
218
220{
221 if (_enabled)
222 {
223 _batchTimer->expires_after(std::chrono::seconds(_updateInterval));
224 _batchTimer->async_wait(std::bind(&Metric::SendBatch, this));
225 }
226 else
227 {
228 static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream()).close();
229 MetricData* data;
230 // Clear the queue
231 while (_queuedData.Dequeue(data))
232 delete data;
233 }
234}
235
237{
238 // Send what's queued only if IoContext is stopped (so only on shutdown)
239 if (_enabled && Trinity::Asio::get_io_context(*_batchTimer).stopped())
240 {
241 _enabled = false;
242 SendBatch();
243 }
244
245 _batchTimer->cancel();
246 _overallStatusTimer->cancel();
247}
248
250{
251 if (_enabled)
252 {
253 _overallStatusTimer->expires_after(std::chrono::seconds(_overallStatusTimerInterval));
254 _overallStatusTimer->async_wait([this](const boost::system::error_code&)
255 {
258 });
259 }
260}
261
262std::string Metric::FormatInfluxDBValue(bool value)
263{
264 return value ? "t" : "f";
265}
266
267template<class T>
268std::string Metric::FormatInfluxDBValue(T value)
269{
270 return std::to_string(value) + 'i';
271}
272
273std::string Metric::FormatInfluxDBValue(std::string const& value)
274{
275 return '"' + boost::replace_all_copy(value, "\"", "\\\"") + '"';
276}
277
278std::string Metric::FormatInfluxDBValue(char const* value)
279{
280 return FormatInfluxDBValue(std::string(value));
281}
282
283std::string Metric::FormatInfluxDBValue(double value)
284{
285 return std::to_string(value);
286}
287
288std::string Metric::FormatInfluxDBValue(float value)
289{
290 return FormatInfluxDBValue(double(value));
291}
292
293std::string Metric::FormatInfluxDBTagValue(std::string const& value)
294{
295 // ToDo: should handle '=' and ',' characters too
296 return boost::replace_all_copy(value, " ", "\\ ");
297}
298
299std::string Metric::FormatInfluxDBValue(std::chrono::nanoseconds value)
300{
301 return FormatInfluxDBValue(std::chrono::duration_cast<Milliseconds>(value).count());
302}
303
305{
306}
307
309{
310}
311
313{
314 static Metric instance;
315 return &instance;
316}
317
#define sConfigMgr
Definition: Config.h:60
uint8_t uint8
Definition: Define.h:135
int64_t int64
Definition: Define.h:128
#define TC_COMMON_API
Definition: Define.h:96
int16_t int16
Definition: Define.h:130
int8_t int8
Definition: Define.h:131
int32_t int32
Definition: Define.h:129
uint64_t uint64
Definition: Define.h:132
uint16_t uint16
Definition: Define.h:134
uint32_t uint32
Definition: Define.h:133
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
@ METRIC_DATA_EVENT
Definition: Metric.h:45
@ METRIC_DATA_VALUE
Definition: Metric.h:44
std::pair< std::string, std::string > MetricTag
Definition: Metric.h:48
Definition: Metric.h:70
std::string _hostname
Definition: Metric.h:81
std::unique_ptr< std::iostream > _dataStream
Definition: Metric.h:73
void ScheduleSend()
Definition: Metric.cpp:219
static std::string FormatInfluxDBTagValue(std::string const &value)
Definition: Metric.cpp:293
std::string _port
Definition: Metric.h:82
std::string _databaseName
Definition: Metric.h:83
static Metric * instance()
Definition: Metric.cpp:312
void ScheduleOverallStatusLog()
Definition: Metric.cpp:249
Metric()
Definition: Metric.cpp:304
bool _overallStatusTimerTriggered
Definition: Metric.h:80
int32 _overallStatusTimerInterval
Definition: Metric.h:78
int32 _updateInterval
Definition: Metric.h:77
std::string _realmName
Definition: Metric.h:85
void Initialize(std::string const &realmName, Trinity::Asio::IoContext &ioContext, std::function< void()> overallStatusLogger)
Definition: Metric.cpp:28
void SendBatch()
Definition: Metric.cpp:140
std::unique_ptr< Trinity::Asio::DeadlineTimer > _overallStatusTimer
Definition: Metric.h:76
void Update()
Definition: Metric.cpp:109
std::iostream & GetDataStream()
Definition: Metric.h:72
std::function< void()> _overallStatusLogger
Definition: Metric.h:84
void Unload()
Definition: Metric.cpp:236
bool _enabled
Definition: Metric.h:79
bool Connect()
Definition: Metric.cpp:38
MPSCQueue< MetricData, &MetricData::QueueLink > _queuedData
Definition: Metric.h:74
bool ShouldLog(std::string const &category, int64 value) const
Definition: Metric.cpp:118
static std::string FormatInfluxDBValue(bool value)
Definition: Metric.cpp:262
std::unique_ptr< Trinity::Asio::DeadlineTimer > _batchTimer
Definition: Metric.h:75
~Metric()
Definition: Metric.cpp:308
void LoadFromConfigs()
Definition: Metric.cpp:54
std::unordered_map< std::string, int64 > _thresholds
Definition: Metric.h:86
void LogEvent(std::string category, std::string title, std::string description)
Definition: Metric.cpp:126
decltype(auto) get_io_context(T &&ioObject)
Definition: IoContext.h:53
TC_COMMON_API std::vector< std::string_view > Tokenize(std::string_view str, char sep, bool keepEmpty)
Definition: Util.cpp:56
std::string Category
Definition: Metric.h:53
SystemTimePoint Timestamp
Definition: Metric.h:54
std::string Title
Definition: Metric.h:61
std::string ValueOrEventText
Definition: Metric.h:63
MetricTagsVector Tags
Definition: Metric.h:58
MetricDataType Type
Definition: Metric.h:55