TrinityCore
Loading...
Searching...
No Matches
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 "Config.h"
20#include "DeadlineTimer.h"
21#include "IoContext.h"
22#include "Log.h"
23#include "Util.h"
24#include <boost/asio/ip/tcp.hpp>
25#include <span>
26
27void Metric::Initialize(std::string const& realmName, Trinity::Asio::IoContext& ioContext, std::function<void()> overallStatusLogger)
28{
29 _dataStream = std::make_unique<boost::asio::ip::tcp::iostream>();
31 _batchTimer = std::make_unique<Trinity::Asio::DeadlineTimer>(ioContext);
32 _overallStatusTimer = std::make_unique<Trinity::Asio::DeadlineTimer>(ioContext);
33 _overallStatusLogger = std::move(overallStatusLogger);
35}
36
38{
39 GetDataStream().connect(_hostname, _port);
40 if (boost::system::error_code const& error = GetDataStream().error())
41 {
42 TC_LOG_ERROR("metric", "Error connecting to '{}:{}', disabling Metric. Error message : {}",
43 _hostname, _port, error.message());
44 _enabled = false;
45 return false;
46 }
47 GetDataStream().clear();
48 return true;
49}
50
52{
53 bool previousValue = _enabled;
54 _enabled = sConfigMgr->GetBoolDefault("Metric.Enable", false);
55 _updateInterval = sConfigMgr->GetIntDefault("Metric.Interval", 1);
56 if (_updateInterval < 1)
57 {
58 TC_LOG_ERROR("metric", "'Metric.Interval' config set to {}, overriding to 1.", _updateInterval);
60 }
61
62 _overallStatusTimerInterval = sConfigMgr->GetIntDefault("Metric.OverallStatusInterval", 1);
64 {
65 TC_LOG_ERROR("metric", "'Metric.OverallStatusInterval' config set to {}, overriding to 1.", _overallStatusTimerInterval);
67 }
68
69 _thresholds.clear();
70 std::vector<std::string> thresholdSettings = sConfigMgr->GetKeysByString("Metric.Threshold.");
71 for (std::string& thresholdSetting : thresholdSettings)
72 {
73 int64 thresholdValue = sConfigMgr->GetIntDefault(thresholdSetting, 0);
74 std::string thresholdName = thresholdSetting.substr(strlen("Metric.Threshold."));
75 _thresholds.emplace(std::move(thresholdName), thresholdValue);
76 }
77
78 // Schedule a send at this point only if the config changed from Disabled to Enabled.
79 // Cancel any scheduled operation if the config changed from Enabled to Disabled.
80 if (_enabled && !previousValue)
81 {
82 std::string connectionInfo = sConfigMgr->GetStringDefault("Metric.ConnectionInfo", "");
83 if (connectionInfo.empty())
84 {
85 TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' not specified in configuration file.");
86 return;
87 }
88
89 std::vector<std::string_view> tokens = Trinity::Tokenize(connectionInfo, ';', true);
90 if (tokens.size() != 3)
91 {
92 TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' specified with wrong format in configuration file.");
93 return;
94 }
95
96 _hostname.assign(tokens[0]);
97 _port.assign(tokens[1]);
98 _databaseName.assign(tokens[2]);
99 Connect();
100
101 ScheduleSend();
103 }
104}
105
114
115bool Metric::ShouldLog(std::string_view category, int64 value) const
116{
117 auto threshold = _thresholds.find(category);
118 if (threshold == _thresholds.end())
119 return false;
120 return value >= threshold->second;
121}
122
123void Metric::LogEvent(std::string_view category, std::string_view title, std::string description)
124{
125 MetricData* data = new MetricData
126 {
127 .Category = std::string(category),
128 .Timestamp = std::chrono::system_clock::now(),
129 .Type = METRIC_DATA_EVENT,
130 .Title = std::string(title),
131 .ValueOrEventText = std::move(description)
132 };
133
134 _queuedData.Enqueue(data);
135}
136
138{
139 using namespace std::chrono;
140
141 std::stringstream batchedData;
142 MetricData* data;
143 bool firstLoop = true;
144 while (_queuedData.Dequeue(data))
145 {
146 if (!firstLoop)
147 batchedData << "\n";
148
149 batchedData << data->Category;
150 if (!_realmName.empty())
151 batchedData << ",realm=" << _realmName;
152
153 auto tags = [](MetricTags const& tags) -> std::span<MetricTag const>
154 {
155 switch (tags.index())
156 {
157 case 1: return std::get<1>(tags);
158 case 2: return std::get<2>(tags);
159 default:
160 break;
161 }
162 return {};
163 }(data->Tags);
164
165 for (auto const& [tagName, tagValue] : tags)
166 if (!tagName.empty())
167 batchedData << "," << tagName << "=" << FormatInfluxDBTagValue(tagValue);
168
169 batchedData << " ";
170
171 switch (data->Type)
172 {
174 batchedData << "value=" << data->ValueOrEventText;
175 break;
177 batchedData << "title=\"" << data->Title << "\",text=\"" << data->ValueOrEventText << "\"";
178 break;
179 }
180
181 batchedData << " ";
182
183 batchedData << duration_cast<nanoseconds>(data->Timestamp.time_since_epoch()).count();
184
185 firstLoop = false;
186 delete data;
187 }
188
189 // Check if there's any data to send
190 std::streamoff batchedDataSize = batchedData.tellp();
191 if (batchedDataSize <= 0)
192 {
193 ScheduleSend();
194 return;
195 }
196
197 if (!GetDataStream().good() && !Connect())
198 return;
199
200 GetDataStream() << "POST " << "/write?db=" << _databaseName << " HTTP/1.1\r\n";
201 GetDataStream() << "Host: " << _hostname << ":" << _port << "\r\n";
202 GetDataStream() << "Accept: */*\r\n";
203 GetDataStream() << "Content-Type: application/octet-stream\r\n";
204 GetDataStream() << "Content-Transfer-Encoding: binary\r\n";
205
206 GetDataStream() << "Content-Length: " << batchedDataSize << "\r\n\r\n";
207 GetDataStream() << batchedData.rdbuf();
208
209 std::string http_version;
210 GetDataStream() >> http_version;
211 unsigned int status_code = 0;
212 GetDataStream() >> status_code;
213 if (status_code != 204)
214 {
215 TC_LOG_ERROR("metric", "Error sending data, returned HTTP code: {}", status_code);
216 }
217
218 // Read and ignore the status description
219 std::string status_description;
220 std::getline(GetDataStream(), status_description);
221 // Read headers
222 std::string header;
223 while (std::getline(GetDataStream(), header) && header != "\r")
224 if (header == "Connection: close\r")
225 GetDataStream().close();
226
227 ScheduleSend();
228}
229
231{
232 if (_enabled)
233 {
234 _batchTimer->expires_after(std::chrono::seconds(_updateInterval));
235 _batchTimer->async_wait([this](boost::system::error_code const&){ SendBatch(); });
236 }
237 else
238 {
239 GetDataStream().close();
240 MetricData* data;
241 // Clear the queue
242 while (_queuedData.Dequeue(data))
243 delete data;
244 }
245}
246
248{
249 // Send what's queued only if IoContext is stopped (so only on shutdown)
250 if (_enabled && Trinity::Asio::get_io_context(*_batchTimer).stopped())
251 {
252 _enabled = false;
253 SendBatch();
254 }
255
256 _batchTimer->cancel();
257 _overallStatusTimer->cancel();
258}
259
261{
262 if (_enabled)
263 {
264 _overallStatusTimer->expires_after(std::chrono::seconds(_overallStatusTimerInterval));
265 _overallStatusTimer->async_wait([this](const boost::system::error_code&)
266 {
269 });
270 }
271}
272
273std::string Metric::FormatInfluxDBValue(bool value)
274{
275 return std::string(1, value ? 't' : 'f');
276}
277
278template<class T> requires std::integral<T> && (!std::same_as<T, bool>)
279std::string Metric::FormatInfluxDBValue(T value)
280{
281 std::string result = std::to_string(value);
282 result += 'i';
283 return result;
284}
285
286std::string Metric::FormatInfluxDBValue(std::string const& value)
287{
288 std::string result = StringReplaceAll(value, "\"", "\\\"");
289 result.insert(result.begin(), '"');
290 result.append(1, '"');
291 return result;
292}
293
294std::string Metric::FormatInfluxDBValue(char const* value)
295{
296 return FormatInfluxDBValue(std::string(value));
297}
298
299std::string Metric::FormatInfluxDBValue(double value)
300{
301 return std::to_string(value);
302}
303
304std::string Metric::FormatInfluxDBValue(float value)
305{
306 return FormatInfluxDBValue(double(value));
307}
308
309std::string Metric::FormatInfluxDBTagValue(std::string const& value)
310{
311 // ToDo: should handle '=' and ',' characters too
312 return StringReplaceAll(value, " ", "\\ ");
313}
314
315std::string Metric::FormatInfluxDBValue(std::chrono::nanoseconds value)
316{
317 return FormatInfluxDBValue(std::chrono::duration_cast<Milliseconds>(value).count());
318}
319
321{
322}
323
325{
326}
327
329{
330 static Metric instance;
331 return &instance;
332}
333
#define sConfigMgr
Definition Config.h:64
uint8_t uint8
Definition Define.h:156
int64_t int64
Definition Define.h:149
#define TC_COMMON_API
Definition Define.h:99
int16_t int16
Definition Define.h:151
int8_t int8
Definition Define.h:152
int32_t int32
Definition Define.h:150
uint64_t uint64
Definition Define.h:153
uint16_t uint16
Definition Define.h:155
uint32_t uint32
Definition Define.h:154
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
@ METRIC_DATA_EVENT
Definition Metric.h:36
@ METRIC_DATA_VALUE
Definition Metric.h:35
std::variant< std::monostate, std::array< MetricTag, 2 >, std::vector< MetricTag > > MetricTags
Definition Metric.h:40
void StringReplaceAll(std::string *str, std::string_view text, std::string_view replacement)
Definition Util.cpp:865
std::string _hostname
Definition Metric.h:74
void ScheduleSend()
Definition Metric.cpp:230
static std::string FormatInfluxDBTagValue(std::string const &value)
Definition Metric.cpp:309
std::string _port
Definition Metric.h:75
bool ShouldLog(std::string_view category, int64 value) const
Definition Metric.cpp:115
std::string _databaseName
Definition Metric.h:76
static Metric * instance()
Definition Metric.cpp:328
void ScheduleOverallStatusLog()
Definition Metric.cpp:260
Metric()
Definition Metric.cpp:320
bool _overallStatusTimerTriggered
Definition Metric.h:73
int32 _overallStatusTimerInterval
Definition Metric.h:71
int32 _updateInterval
Definition Metric.h:70
std::string _realmName
Definition Metric.h:78
void Initialize(std::string const &realmName, Trinity::Asio::IoContext &ioContext, std::function< void()> overallStatusLogger)
Definition Metric.cpp:27
void SendBatch()
Definition Metric.cpp:137
std::unique_ptr< Trinity::Asio::DeadlineTimer > _overallStatusTimer
Definition Metric.h:69
void Update()
Definition Metric.cpp:106
std::function< void()> _overallStatusLogger
Definition Metric.h:77
void Unload()
Definition Metric.cpp:247
std::unique_ptr< iostream > _dataStream
Definition Metric.h:66
bool _enabled
Definition Metric.h:72
bool Connect()
Definition Metric.cpp:37
MPSCQueue< MetricData, &MetricData::QueueLink > _queuedData
Definition Metric.h:67
std::unordered_map< std::string, int64, Trinity::TransparentHash< std::string_view >, std::equal_to<> > _thresholds
Definition Metric.h:79
static std::string FormatInfluxDBValue(bool value)
Definition Metric.cpp:273
std::unique_ptr< Trinity::Asio::DeadlineTimer > _batchTimer
Definition Metric.h:68
~Metric()
Definition Metric.cpp:324
void LoadFromConfigs()
Definition Metric.cpp:51
void LogEvent(std::string_view category, std::string_view title, std::string description)
Definition Metric.cpp:123
iostream & GetDataStream()
Definition Metric.h:65
decltype(auto) get_io_context(T &&ioObject)
Definition IoContext.h:68
TC_COMMON_API std::vector< std::string_view > Tokenize(std::string_view str, char sep, bool keepEmpty)
Definition Util.cpp:57
std::string Category
Definition Metric.h:44
SystemTimePoint Timestamp
Definition Metric.h:45
std::string Title
Definition Metric.h:52
std::string ValueOrEventText
Definition Metric.h:54
MetricTags Tags
Definition Metric.h:49
MetricDataType Type
Definition Metric.h:46