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