Core/Metric: Reduce number of string copies in metric and eliminate them entirely when metric is disabled in config

Signed-off-by: luis <[email protected]>
This commit is contained in:
Shauren
2026-01-21 02:31:10 -03:00
committed by luis
parent 214e2b8172
commit ecf78b5143
3 changed files with 133 additions and 132 deletions
+2 -4
View File
@@ -365,8 +365,7 @@ namespace VMAP
}
else
iLoadedTiles[packTileID(tileX, tileY)] = false;
TC_METRIC_EVENT("map_events", "LoadMapTile",
"Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
TC_METRIC_EVENT("map_events", "LoadMapTile", Trinity::StringFormat("Map: {} TileX: {} TileY: {}", iMapID, tileX, tileY));
return result;
}
@@ -432,8 +431,7 @@ namespace VMAP
}
}
iLoadedTiles.erase(tile);
TC_METRIC_EVENT("map_events", "UnloadMapTile",
"Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
TC_METRIC_EVENT("map_events", "UnloadMapTile", Trinity::StringFormat("Map: {} TileX: {} TileY: {}", iMapID, tileX, tileY));
}
std::span<ModelInstance const> StaticMapTree::getModelInstances() const
+39 -31
View File
@@ -22,6 +22,7 @@
#include "Log.h"
#include "Util.h"
#include <boost/asio/ip/tcp.hpp>
#include <span>
void Metric::Initialize(std::string const& realmName, Trinity::Asio::IoContext& ioContext, std::function<void()> overallStatusLogger)
{
@@ -29,22 +30,21 @@ void Metric::Initialize(std::string const& realmName, Trinity::Asio::IoContext&
_realmName = FormatInfluxDBTagValue(realmName);
_batchTimer = std::make_unique<Trinity::Asio::DeadlineTimer>(ioContext);
_overallStatusTimer = std::make_unique<Trinity::Asio::DeadlineTimer>(ioContext);
_overallStatusLogger = overallStatusLogger;
_overallStatusLogger = std::move(overallStatusLogger);
LoadFromConfigs();
}
bool Metric::Connect()
{
auto& stream = static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream());
stream.connect(_hostname, _port);
if (boost::system::error_code const& error = stream.error())
GetDataStream().connect(_hostname, _port);
if (boost::system::error_code const& error = GetDataStream().error())
{
TC_LOG_ERROR("metric", "Error connecting to '{}:{}', disabling Metric. Error message : {}",
_hostname, _port, error.message());
_enabled = false;
return false;
}
stream.clear();
GetDataStream().clear();
return true;
}
@@ -68,11 +68,11 @@ void Metric::LoadFromConfigs()
_thresholds.clear();
std::vector<std::string> thresholdSettings = sConfigMgr->GetKeysByString("Metric.Threshold.");
for (std::string const& thresholdSetting : thresholdSettings)
for (std::string& thresholdSetting : thresholdSettings)
{
int thresholdValue = sConfigMgr->GetIntDefault(thresholdSetting, 0);
int64 thresholdValue = sConfigMgr->GetIntDefault(thresholdSetting, 0);
std::string thresholdName = thresholdSetting.substr(strlen("Metric.Threshold."));
_thresholds[thresholdName] = thresholdValue;
_thresholds.emplace(std::move(thresholdName), thresholdValue);
}
// Schedule a send at this point only if the config changed from Disabled to Enabled.
@@ -112,7 +112,7 @@ void Metric::Update()
}
}
bool Metric::ShouldLog(std::string const& category, int64 value) const
bool Metric::ShouldLog(std::string_view category, int64 value) const
{
auto threshold = _thresholds.find(category);
if (threshold == _thresholds.end())
@@ -120,16 +120,16 @@ bool Metric::ShouldLog(std::string const& category, int64 value) const
return value >= threshold->second;
}
void Metric::LogEvent(std::string category, std::string title, std::string description)
void Metric::LogEvent(std::string_view category, std::string_view title, std::string description)
{
using namespace std::chrono;
MetricData* data = new MetricData;
data->Category = std::move(category);
data->Timestamp = system_clock::now();
data->Type = METRIC_DATA_EVENT;
data->Title = std::move(title);
data->ValueOrEventText = std::move(description);
MetricData* data = new MetricData
{
.Category = std::string(category),
.Timestamp = std::chrono::system_clock::now(),
.Type = METRIC_DATA_EVENT,
.Title = std::string(title),
.ValueOrEventText = std::move(description)
};
_queuedData.Enqueue(data);
}
@@ -150,14 +150,21 @@ void Metric::SendBatch()
if (!_realmName.empty())
batchedData << ",realm=" << _realmName;
if (data->Tags)
auto tags = [](MetricTags const& tags) -> std::span<MetricTag const>
{
auto begin = std::visit([](auto&& value) { return value.data(); }, *data->Tags);
auto end = std::visit([](auto&& value) { return value.data() + value.size(); }, *data->Tags);
for (auto itr = begin; itr != end; ++itr)
if (!itr->first.empty())
batchedData << "," << itr->first << "=" << FormatInfluxDBTagValue(itr->second);
}
switch (tags.index())
{
case 1: return std::get<1>(tags);
case 2: return std::get<2>(tags);
default:
break;
}
return {};
}(data->Tags);
for (auto const& [tagName, tagValue] : tags)
if (!tagName.empty())
batchedData << "," << tagName << "=" << FormatInfluxDBTagValue(tagValue);
batchedData << " ";
@@ -173,14 +180,15 @@ void Metric::SendBatch()
batchedData << " ";
batchedData << std::to_string(duration_cast<nanoseconds>(data->Timestamp.time_since_epoch()).count());
batchedData << duration_cast<nanoseconds>(data->Timestamp.time_since_epoch()).count();
firstLoop = false;
delete data;
}
// Check if there's any data to send
if (batchedData.tellp() == std::streampos(0))
std::streamoff batchedDataSize = batchedData.tellp();
if (batchedDataSize <= 0)
{
ScheduleSend();
return;
@@ -195,7 +203,7 @@ void Metric::SendBatch()
GetDataStream() << "Content-Type: application/octet-stream\r\n";
GetDataStream() << "Content-Transfer-Encoding: binary\r\n";
GetDataStream() << "Content-Length: " << std::to_string(batchedData.tellp()) << "\r\n\r\n";
GetDataStream() << "Content-Length: " << batchedDataSize << "\r\n\r\n";
GetDataStream() << batchedData.rdbuf();
std::string http_version;
@@ -214,7 +222,7 @@ void Metric::SendBatch()
std::string header;
while (std::getline(GetDataStream(), header) && header != "\r")
if (header == "Connection: close\r")
static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream()).close();
GetDataStream().close();
ScheduleSend();
}
@@ -228,7 +236,7 @@ void Metric::ScheduleSend()
}
else
{
static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream()).close();
GetDataStream().close();
MetricData* data;
// Clear the queue
while (_queuedData.Dequeue(data))
@@ -267,7 +275,7 @@ std::string Metric::FormatInfluxDBValue(bool value)
return std::string(1, value ? 't' : 'f');
}
template<class T>
template<class T> requires std::integral<T> && (!std::same_as<T, bool>)
std::string Metric::FormatInfluxDBValue(T value)
{
std::string result = std::to_string(value);
+92 -97
View File
@@ -15,30 +15,21 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef METRIC_H__
#define METRIC_H__
#ifndef TRINITYCORE_METRIC_H
#define TRINITYCORE_METRIC_H
#include "AsioHacksFwd.h"
#include "Define.h"
#include "Duration.h"
#include "Hash.h"
#include "MPSCQueue.h"
#include "Optional.h"
#include <functional>
#include <iosfwd>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <variant>
namespace Trinity
{
namespace Asio
{
class IoContext;
class DeadlineTimer;
}
}
enum MetricDataType
{
METRIC_DATA_VALUE,
@@ -46,6 +37,7 @@ enum MetricDataType
};
using MetricTag = std::pair<std::string, std::string>;
using MetricTags = std::variant<std::monostate, std::array<MetricTag, 2>, std::vector<MetricTag>>;
struct MetricData
{
@@ -54,7 +46,7 @@ struct MetricData
MetricDataType Type = METRIC_DATA_VALUE;
// LogValue-specific fields
Optional<std::variant<std::array<MetricTag, 2>, std::vector<MetricTag>>> Tags;
MetricTags Tags;
// LogEvent-specific fields
std::string Title;
@@ -68,8 +60,10 @@ struct MetricData
class TC_COMMON_API Metric
{
private:
std::iostream& GetDataStream() { return *_dataStream; }
std::unique_ptr<std::iostream> _dataStream;
using iostream = boost::asio::basic_socket_iostream<boost::asio::ip::tcp, std::chrono::steady_clock, boost::asio::wait_traits<std::chrono::steady_clock>>;
iostream& GetDataStream() { return *_dataStream; }
std::unique_ptr<iostream> _dataStream;
MPSCQueue<MetricData, &MetricData::QueueLink> _queuedData;
std::unique_ptr<Trinity::Asio::DeadlineTimer> _batchTimer;
std::unique_ptr<Trinity::Asio::DeadlineTimer> _overallStatusTimer;
@@ -82,7 +76,7 @@ private:
std::string _databaseName;
std::function<void()> _overallStatusLogger;
std::string _realmName;
std::unordered_map<std::string, int64> _thresholds;
std::unordered_map<std::string, int64, Trinity::TransparentHash<std::string_view>, std::equal_to<>> _thresholds;
bool Connect();
void SendBatch();
@@ -90,7 +84,7 @@ private:
void ScheduleOverallStatusLog();
static std::string FormatInfluxDBValue(bool value);
template <class T>
template <class T> requires std::integral<T> && (!std::same_as<T, bool>)
static std::string FormatInfluxDBValue(T value);
static std::string FormatInfluxDBValue(std::string const& value);
static std::string FormatInfluxDBValue(char const* value);
@@ -114,37 +108,50 @@ public:
void Initialize(std::string const& realmName, Trinity::Asio::IoContext& ioContext, std::function<void()> overallStatusLogger);
void LoadFromConfigs();
void Update();
bool ShouldLog(std::string const& category, int64 value) const;
bool ShouldLog(std::string_view category, int64 value) const;
template<class T, class... TagsList>
void LogValue(std::string category, T value, TagsList&&... tags)
void LogValue(std::string_view category, T value, TagsList&&... tags) noexcept
{
using namespace std::chrono;
using TagsType = std::conditional_t<(sizeof...(tags) > 2),
std::vector<MetricTag>,
std::conditional_t<(sizeof...(tags) > 0),
std::array<MetricTag, 2>,
std::monostate>>;
MetricData* data = new MetricData;
data->Category = std::move(category);
data->Timestamp = system_clock::now();
data->Type = METRIC_DATA_VALUE;
data->ValueOrEventText = FormatInfluxDBValue(value);
if constexpr (sizeof...(tags) > 0)
MetricData* data = new MetricData
{
data->Tags.emplace();
if constexpr (sizeof...(tags) > 2)
.Category = std::string(category),
.Timestamp = std::chrono::system_clock::now(),
.Type = METRIC_DATA_VALUE,
.Tags = MetricTags{ std::in_place_type<TagsType> },
.ValueOrEventText = FormatInfluxDBValue(value)
};
if constexpr (sizeof...(tags) > 2)
{
TagsType& tagsVector = std::get<TagsType>(data->Tags);
(tagsVector.emplace_back(std::forward<TagsList>(tags)), ...);
}
else if constexpr (sizeof...(tags) == 2)
{
[](TagsType& tagsArray, auto const& tag1, auto const& tag2)
{
decltype(auto) tagsVector = data->Tags->emplace<1>();
(tagsVector.emplace_back(std::move(tags)), ...);
}
else
tagsArray[0] = tag1;
tagsArray[1] = tag2;
}(std::get<TagsType>(data->Tags), std::forward<TagsList>(tags)...);
}
else if constexpr (sizeof...(tags) == 1)
{
[](TagsType& tagsArray, auto const& tag1)
{
decltype(auto) tagsArray = data->Tags->emplace<0>();
tagsArray = { std::move(tags)... };
}
tagsArray[0] = tag1;
}(std::get<TagsType>(data->Tags), std::forward<TagsList>(tags)...);
}
_queuedData.Enqueue(data);
}
void LogEvent(std::string category, std::string title, std::string description);
void LogEvent(std::string_view category, std::string_view title, std::string description);
void Unload();
bool IsEnabled() const { return _enabled; }
@@ -152,36 +159,32 @@ public:
#define sMetric Metric::instance()
template<typename LoggerType>
class MetricStopWatch
struct MetricStopWatchBase
{
public:
MetricStopWatch(LoggerType&& loggerFunc) :
_logger(std::forward<LoggerType>(loggerFunc)),
_startTime(std::chrono::steady_clock::now())
void Enable(Metric* metric) noexcept
{
MetricInstance = metric;
StartTime = std::chrono::steady_clock::now();
}
~MetricStopWatch()
{
_logger(_startTime);
}
private:
LoggerType _logger;
TimePoint _startTime;
Metric* MetricInstance = nullptr;
TimePoint StartTime = { };
};
template<typename LoggerType>
Optional<MetricStopWatch<LoggerType>> MakeMetricStopWatch(LoggerType&& loggerFunc)
struct MetricStopWatch : public LoggerType, public MetricStopWatchBase
{
if (!sMetric->IsEnabled())
return {};
~MetricStopWatch()
{
if (MetricInstance)
LoggerType::operator()(MetricInstance, StartTime);
}
};
return Optional<MetricStopWatch<LoggerType>>(std::in_place, std::forward<LoggerType>(loggerFunc));
}
template<typename LoggerType>
MetricStopWatch(LoggerType) -> MetricStopWatch<LoggerType>;
#define TC_METRIC_TAG(name, value) MetricTag(name, value)
#define TC_METRIC_TAG(name, value) std::pair<std::string_view, std::string_view>(name, value)
#define TC_METRIC_DO_CONCAT(a, b) a ## b
#define TC_METRIC_CONCAT(a, b) TC_METRIC_DO_CONCAT(a, b)
@@ -195,50 +198,42 @@ Optional<MetricStopWatch<LoggerType>> MakeMetricStopWatch(LoggerType&& loggerFun
#define TC_METRIC_DETAILED_TIMER(category, ...) ((void)0)
#define TC_METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) ((void)0)
#else
# if TRINITY_PLATFORM != TRINITY_PLATFORM_WINDOWS
#define TC_METRIC_EVENT(category, title, description) \
do { \
if (sMetric->IsEnabled()) \
sMetric->LogEvent(category, title, description); \
} while (0)
if (Metric* metric = sMetric; metric->IsEnabled()) \
metric->LogEvent(category, title, description); \
} while (false)
#define TC_METRIC_VALUE(category, value, ...) \
do { \
if (sMetric->IsEnabled()) \
sMetric->LogValue(category, value, ##__VA_ARGS__); \
} while (0)
# else
#define TC_METRIC_EVENT(category, title, description) \
__pragma(warning(push)) \
__pragma(warning(disable:4127)) \
do { \
if (sMetric->IsEnabled()) \
sMetric->LogEvent(category, title, description); \
} while (0) \
__pragma(warning(pop))
#define TC_METRIC_VALUE(category, value, ...) \
__pragma(warning(push)) \
__pragma(warning(disable:4127)) \
do { \
if (sMetric->IsEnabled()) \
sMetric->LogValue(category, value, ##__VA_ARGS__); \
} while (0) \
__pragma(warning(pop))
# endif
#define TC_METRIC_TIMER(category, ...) \
auto TC_METRIC_UNIQUE_NAME(__tc_metric_stop_watch) = MakeMetricStopWatch([&](TimePoint start) \
{ \
sMetric->LogValue(category, std::chrono::steady_clock::now() - start, ##__VA_ARGS__); \
});
if (Metric* metric = sMetric; metric->IsEnabled()) \
metric->LogValue(category, value, ## __VA_ARGS__); \
} while (false)
#define TC_METRIC_TIMER_IMPL(variable, category, ...) \
MetricStopWatch variable{ [&](Metric* metric, TimePoint start) \
{ \
metric->LogValue(category, std::chrono::steady_clock::now() - start, ## __VA_ARGS__); \
} }; \
do \
{ \
if (Metric* metric = sMetric; metric->IsEnabled()) \
variable.Enable(metric); \
} while (false)
#define TC_METRIC_TIMER(category, ...) TC_METRIC_TIMER_IMPL(TC_METRIC_UNIQUE_NAME(tc_metric_stop_watch_), category, ## __VA_ARGS__)
# if defined WITH_DETAILED_METRICS
#define TC_METRIC_DETAILED_TIMER(category, ...) \
auto TC_METRIC_UNIQUE_NAME(__tc_metric_stop_watch) = MakeMetricStopWatch([&](TimePoint start) \
{ \
int64 duration = int64(std::chrono::duration_cast<Milliseconds>(std::chrono::steady_clock::now() - start).count()); \
std::string category2 = category; \
if (sMetric->ShouldLog(category2, duration)) \
sMetric->LogValue(std::move(category2), duration, ##__VA_ARGS__); \
});
#define TC_METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) TC_METRIC_TIMER(category, ##__VA_ARGS__)
#define TC_METRIC_DETAILED_TIMER_IMPL(variable, category, ...) \
MetricStopWatch variable{ [&](Metric* metric, TimePoint start) \
{ \
int64 duration = int64(duration_cast<Milliseconds>(std::chrono::steady_clock::now() - start).count()); \
if (metric->ShouldLog(category, duration)) \
metric->LogValue(category, duration, ## __VA_ARGS__); \
} }; \
do \
{ \
if (Metric* metric = sMetric; metric->IsEnabled()) \
variable.Enable(metric); \
} while (false)
#define TC_METRIC_DETAILED_TIMER(category, ...) TC_METRIC_DETAILED_TIMER_IMPL(TC_METRIC_UNIQUE_NAME(tc_metric_stop_watch_), category, ## __VA_ARGS__)
#define TC_METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) TC_METRIC_TIMER(category, ## __VA_ARGS__)
#define TC_METRIC_DETAILED_EVENT(category, title, description) TC_METRIC_EVENT(category, title, description)
# else
#define TC_METRIC_DETAILED_EVENT(category, title, description) ((void)0)
@@ -248,4 +243,4 @@ Optional<MetricStopWatch<LoggerType>> MakeMetricStopWatch(LoggerType&& loggerFun
#endif
#endif // METRIC_H__
#endif // TRINITYCORE_METRIC_H