aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/Collision/Maps/MapTree.cpp5
-rw-r--r--src/common/Metric/Metric.cpp235
-rw-r--r--src/common/Metric/Metric.h141
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp3
-rw-r--r--src/server/game/Movement/PathGenerator.cpp3
-rw-r--r--src/server/game/Server/WorldSession.cpp5
-rw-r--r--src/server/game/World/World.cpp8
-rw-r--r--src/server/worldserver/Main.cpp11
-rw-r--r--src/server/worldserver/worldserver.conf.dist39
9 files changed, 450 insertions, 0 deletions
diff --git a/src/common/Collision/Maps/MapTree.cpp b/src/common/Collision/Maps/MapTree.cpp
index 4d0996e1961..5907971efb9 100644
--- a/src/common/Collision/Maps/MapTree.cpp
+++ b/src/common/Collision/Maps/MapTree.cpp
@@ -22,6 +22,7 @@
#include "VMapDefinitions.h"
#include "Log.h"
#include "Errors.h"
+#include "Metric.h"
#include <string>
#include <sstream>
@@ -415,6 +416,8 @@ 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));
return result;
}
@@ -473,6 +476,8 @@ 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));
}
void StaticMapTree::getModelInstances(ModelInstance* &models, uint32 &count)
diff --git a/src/common/Metric/Metric.cpp b/src/common/Metric/Metric.cpp
new file mode 100644
index 00000000000..9484cebcc72
--- /dev/null
+++ b/src/common/Metric/Metric.cpp
@@ -0,0 +1,235 @@
+/*
+* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+* more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "Metric.h"
+#include "Log.h"
+#include "Config.h"
+#include "Util.h"
+
+void Metric::Initialize(std::string const& realmName, boost::asio::io_service& ioService, std::function<void()> overallStatusLogger)
+{
+ _realmName = realmName;
+ _batchTimer = Trinity::make_unique<boost::asio::deadline_timer>(ioService);
+ _overallStatusTimer = Trinity::make_unique<boost::asio::deadline_timer>(ioService);
+ _overallStatusLogger = overallStatusLogger;
+ LoadFromConfigs();
+}
+
+bool Metric::Connect()
+{
+ _dataStream.connect(_hostname, _port);
+ auto error = _dataStream.error();
+ if (error)
+ {
+ TC_LOG_ERROR("metric", "Error connecting to '%s:%s', disabling Metric. Error message : %s",
+ _hostname.c_str(), _port.c_str(), error.message().c_str());
+ _enabled = false;
+ return false;
+ }
+ _dataStream.clear();
+ return true;
+}
+
+void Metric::LoadFromConfigs()
+{
+ bool previousValue = _enabled;
+ _enabled = sConfigMgr->GetBoolDefault("Metric.Enable", false);
+ _updateInterval = sConfigMgr->GetIntDefault("Metric.Interval", 10);
+ if (_updateInterval < 1)
+ {
+ TC_LOG_ERROR("metric", "'Metric.Interval' config set to %d, overriding to 1.", _updateInterval);
+ _updateInterval = 1;
+ }
+
+ _overallStatusTimerInterval = sConfigMgr->GetIntDefault("Metric.OverallStatusInterval", 1);
+ if (_overallStatusTimerInterval < 1)
+ {
+ TC_LOG_ERROR("metric", "'Metric.OverallStatusInterval' config set to %d, overriding to 1.", _overallStatusTimerInterval);
+ _overallStatusTimerInterval = 1;
+ }
+
+ // Schedule a send at this point only if the config changed from Disabled to Enabled.
+ // Cancel any scheduled operation if the config changed from Enabled to Disabled.
+ if (_enabled && !previousValue)
+ {
+ std::string connectionInfo = sConfigMgr->GetStringDefault("Metric.ConnectionInfo", "");
+ if (connectionInfo.empty())
+ {
+ TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' not specified in configuration file.");
+ return;
+ }
+
+ Tokenizer tokens(connectionInfo, ';');
+ if (tokens.size() != 3)
+ {
+ TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' specified with wrong format in configuration file.");
+ return;
+ }
+
+ _hostname.assign(tokens[0]);
+ _port.assign(tokens[1]);
+ _databaseName.assign(tokens[2]);
+ Connect();
+
+ ScheduleSend();
+ ScheduleOverallStatusLog();
+ }
+}
+
+void Metric::Update()
+{
+ if (_overallStatusTimerTriggered)
+ {
+ _overallStatusTimerTriggered = false;
+ _overallStatusLogger();
+ }
+}
+
+void Metric::LogEvent(std::string const& category, std::string const& title, std::string const& description)
+{
+ using namespace std::chrono;
+
+ MetricData* data = new MetricData;
+ data->Category = category;
+ data->Timestamp = system_clock::now();
+ data->Type = METRIC_DATA_EVENT;
+ data->Title = title;
+ data->Text = description;
+
+ _queuedData.Enqueue(data);
+}
+
+void Metric::SendBatch()
+{
+ using namespace std::chrono;
+
+ std::stringstream batchedData;
+ MetricData* data;
+ bool firstLoop = true;
+ while (_queuedData.Dequeue(data))
+ {
+ if (!firstLoop)
+ batchedData << "\n";
+
+ batchedData << data->Category;
+ if (!_realmName.empty())
+ batchedData << ",realm=" << _realmName;
+
+ batchedData << " ";
+
+ switch (data->Type)
+ {
+ case METRIC_DATA_VALUE:
+ batchedData << "value=" << data->Value;
+ break;
+ case METRIC_DATA_EVENT:
+ batchedData << "title=\"" << data->Title << "\",text=\"" << data->Text << "\"";
+ break;
+ }
+
+ batchedData << " ";
+
+ batchedData << std::to_string(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))
+ {
+ ScheduleSend();
+ return;
+ }
+
+ if (!_dataStream.good() && !Connect())
+ return;
+
+ _dataStream << "POST " << "/write?db=" << _databaseName << " HTTP/1.1\r\n";
+ _dataStream << "Host: " << _hostname << ":" << _port << "\r\n";
+ _dataStream << "Accept: */*\r\n";
+ _dataStream << "Content-Type: application/octet-stream\r\n";
+ _dataStream << "Content-Transfer-Encoding: binary\r\n";
+
+ _dataStream << "Content-Length: " << std::to_string(batchedData.tellp()) << "\r\n\r\n";
+ _dataStream << batchedData.rdbuf();
+
+ std::string http_version;
+ _dataStream >> http_version;
+ unsigned int status_code = 0;
+ _dataStream >> status_code;
+ if (status_code != 204)
+ {
+ TC_LOG_ERROR("metric", "Error sending data, returned HTTP code: %u", status_code);
+ }
+
+ // Read and ignore the status description
+ std::string status_description;
+ std::getline(_dataStream, status_description);
+ // Read headers
+ std::string header;
+ while (std::getline(_dataStream, header) && header != "\r")
+ {
+ if (header == "Connection: close\r")
+ _dataStream.close();
+ }
+
+ ScheduleSend();
+}
+
+void Metric::ScheduleSend()
+{
+ if (_enabled)
+ {
+ _batchTimer->expires_from_now(boost::posix_time::seconds(_updateInterval));
+ _batchTimer->async_wait(std::bind(&Metric::SendBatch, this));
+ }
+ else
+ {
+ _dataStream.close();
+ MetricData* data;
+ // Clear the queue
+ while (_queuedData.Dequeue(data))
+ ;
+ }
+}
+
+void Metric::ForceSend()
+{
+ // Send what's queued only if io_service is stopped (so only on shutdown)
+ if (_enabled && _batchTimer->get_io_service().stopped())
+ SendBatch();
+}
+
+void Metric::ScheduleOverallStatusLog()
+{
+ if (_enabled)
+ {
+ _overallStatusTimer->expires_from_now(boost::posix_time::seconds(_overallStatusTimerInterval));
+ _overallStatusTimer->async_wait([this](const boost::system::error_code&)
+ {
+ _overallStatusTimerTriggered = true;
+ ScheduleOverallStatusLog();
+ });
+ }
+}
+
+Metric* Metric::instance()
+{
+ static Metric instance;
+ return &instance;
+}
diff --git a/src/common/Metric/Metric.h b/src/common/Metric/Metric.h
new file mode 100644
index 00000000000..1855e1d0098
--- /dev/null
+++ b/src/common/Metric/Metric.h
@@ -0,0 +1,141 @@
+/*
+* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+* more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef METRIC_H__
+#define METRIC_H__
+
+#include "Common.h"
+#include "Threading/MPSCQueue.h"
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/algorithm/string.hpp>
+#include <type_traits>
+
+enum MetricDataType
+{
+ METRIC_DATA_VALUE,
+ METRIC_DATA_EVENT
+};
+
+struct MetricData
+{
+ std::string Category;
+ std::chrono::time_point<std::chrono::system_clock> Timestamp;
+ MetricDataType Type;
+
+ // LogValue-specific fields
+ std::string Value;
+
+ // LogEvent-specific fields
+ std::string Title;
+ std::string Text;
+};
+
+class TC_COMMON_API Metric
+{
+private:
+ boost::asio::ip::tcp::iostream _dataStream;
+ MPSCQueue<MetricData> _queuedData;
+ std::unique_ptr<boost::asio::deadline_timer> _batchTimer;
+ std::unique_ptr<boost::asio::deadline_timer> _overallStatusTimer;
+ int32 _updateInterval = 0;
+ int32 _overallStatusTimerInterval = 0;
+ bool _enabled = false;
+ bool _overallStatusTimerTriggered = false;
+ std::string _hostname;
+ std::string _port;
+ std::string _databaseName;
+ std::function<void()> _overallStatusLogger;
+ std::string _realmName;
+
+ bool Connect();
+ void SendBatch();
+ void ScheduleSend();
+ void ScheduleOverallStatusLog();
+
+ template<class T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+ static std::string FormatInfluxDBValue(T value) { return std::to_string(value) + 'i'; }
+
+ static std::string FormatInfluxDBValue(std::string const& value)
+ {
+ return '"' + boost::replace_all_copy(value, "\"", "\\\"") + '"';
+ }
+
+ static std::string FormatInfluxDBValue(bool value) { return value ? "t" : "f"; }
+ static std::string FormatInfluxDBValue(const char* value) { return FormatInfluxDBValue(std::string(value)); }
+ static std::string FormatInfluxDBValue(double value) { return std::to_string(value); }
+ static std::string FormatInfluxDBValue(float value) { return FormatInfluxDBValue(double(value)); }
+
+public:
+ static Metric* instance();
+
+ void Initialize(std::string const& realmName, boost::asio::io_service& ioService, std::function<void()> overallStatusLogger = [](){});
+ void LoadFromConfigs();
+ void Update();
+
+ template<class T>
+ void LogValue(std::string const& category, T value)
+ {
+ using namespace std::chrono;
+
+ MetricData* data = new MetricData;
+ data->Category = category;
+ data->Timestamp = system_clock::now();
+ data->Type = METRIC_DATA_VALUE;
+ data->Value = FormatInfluxDBValue(value);
+
+ _queuedData.Enqueue(data);
+ }
+
+ void LogEvent(std::string const& category, std::string const& title, std::string const& description);
+
+ void ForceSend();
+ bool IsEnabled() const { return _enabled; }
+};
+
+#define sMetric Metric::instance()
+
+#if PLATFORM != PLATFORM_WINDOWS
+#define TC_METRIC_EVENT(category, title, description) \
+ do { \
+ if (sMetric->IsEnabled()) \
+ sMetric->LogEvent(category, title, description); \
+ } while (0)
+#define TC_METRIC_VALUE(category, value) \
+ do { \
+ if (sMetric->IsEnabled()) \
+ sMetric->LogValue(category, value); \
+ } 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); \
+ } while (0) \
+ __pragma(warning(pop))
+#endif
+
+#endif // METRIC_H__
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index 19638ec1bf8..a265b2760f5 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -45,6 +45,7 @@
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
+#include "Metric.h"
class LoginQueryHolder : public SQLQueryHolder
@@ -995,6 +996,8 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder)
sScriptMgr->OnPlayerLogin(pCurrChar, firstLogin);
+ TC_METRIC_EVENT("player_events", "Login", pCurrChar->GetName());
+
delete holder;
}
diff --git a/src/server/game/Movement/PathGenerator.cpp b/src/server/game/Movement/PathGenerator.cpp
index 3b4f19adb0b..45d8401bcba 100644
--- a/src/server/game/Movement/PathGenerator.cpp
+++ b/src/server/game/Movement/PathGenerator.cpp
@@ -25,6 +25,7 @@
#include "DisableMgr.h"
#include "DetourCommon.h"
#include "DetourNavMeshQuery.h"
+#include "Metric.h"
////////////////// PathGenerator //////////////////
PathGenerator::PathGenerator(const Unit* owner) :
@@ -61,6 +62,8 @@ bool PathGenerator::CalculatePath(float destX, float destY, float destZ, bool fo
if (!Trinity::IsValidMapCoord(destX, destY, destZ) || !Trinity::IsValidMapCoord(x, y, z))
return false;
+ TC_METRIC_EVENT("mmap_events", "CalculatePath", "");
+
G3D::Vector3 dest(destX, destY, destZ);
SetEndPosition(dest);
diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp
index ddcc10b53dd..b774463e8fd 100644
--- a/src/server/game/Server/WorldSession.cpp
+++ b/src/server/game/Server/WorldSession.cpp
@@ -43,6 +43,7 @@
#include "WardenWin.h"
#include "MoveSpline.h"
#include "WardenMac.h"
+#include "Metric.h"
#include <zlib.h>
@@ -380,6 +381,8 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
break;
}
+ TC_METRIC_VALUE("processed_packets", processedPackets);
+
_recvQueue.readd(requeuePackets.begin(), requeuePackets.end());
if (m_Socket && m_Socket->IsOpen() && _warden)
@@ -536,6 +539,8 @@ void WorldSession::LogoutPlayer(bool save)
//! Call script hook before deletion
sScriptMgr->OnPlayerLogout(_player);
+ TC_METRIC_EVENT("player_events", "Logout", _player->GetName());
+
//! Remove the player from the world
// the player may not be in the world when logging out
// e.g if he got disconnected during a transfer to another map
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index 37457f36145..b5c5b9f82b6 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -58,6 +58,7 @@
#include "SkillDiscovery.h"
#include "SkillExtraItems.h"
#include "SmartAI.h"
+#include "Metric.h"
#include "TicketMgr.h"
#include "TransportMgr.h"
#include "Unit.h"
@@ -396,6 +397,7 @@ void World::LoadConfigSettings(bool reload)
return;
}
sLog->LoadFromConfig();
+ sMetric->LoadFromConfigs();
}
///- Read the player limit and the Message of the day from the config file
@@ -1937,6 +1939,8 @@ void World::SetInitialWorldSettings()
TC_LOG_INFO("server.worldserver", "World initialized in %u minutes %u seconds", (startupDuration / 60000), ((startupDuration % 60000) / 1000));
+ TC_METRIC_EVENT("events", "World initialized", "World initialized in " + std::to_string(startupDuration / 60000) + " minutes " + std::to_string((startupDuration % 60000) / 1000) + " seconds");
+
if (uint32 realmId = sConfigMgr->GetIntDefault("RealmID", 0)) // 0 reserved for auth
sLog->SetRealmId(realmId);
}
@@ -2251,6 +2255,10 @@ void World::Update(uint32 diff)
ProcessCliCommands();
sScriptMgr->OnWorldUpdate(diff);
+
+ // Stats logger update
+ sMetric->Update();
+ TC_METRIC_VALUE("update_time_diff", diff);
}
void World::ForceGameEventUpdate()
diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp
index f4d736ac675..0241221a2ac 100644
--- a/src/server/worldserver/Main.cpp
+++ b/src/server/worldserver/Main.cpp
@@ -52,6 +52,7 @@
#include "Realm/Realm.h"
#include "DatabaseLoader.h"
#include "AppenderDB.h"
+#include "Metric.h"
using namespace boost::program_options;
namespace fs = boost::filesystem;
@@ -196,6 +197,13 @@ extern int main(int argc, char** argv)
LoadRealmInfo();
+ sMetric->Initialize(realm.Name, _ioService, []()
+ {
+ TC_METRIC_VALUE("online_players", sWorld->GetPlayerCount());
+ });
+
+ TC_METRIC_EVENT("events", "Worldserver started", "");
+
// Initialize the World
sScriptMgr->SetScriptLoader(AddScripts);
sWorld->SetInitialWorldSettings();
@@ -295,6 +303,9 @@ extern int main(int argc, char** argv)
StopDB();
+ TC_METRIC_EVENT("events", "Worldserver shutdown", "");
+ sMetric->ForceSend();
+
TC_LOG_INFO("server.worldserver", "Halting process...");
ShutdownCLIThread(cliThread);
diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist
index e620404e91a..acaabca50fd 100644
--- a/src/server/worldserver/worldserver.conf.dist
+++ b/src/server/worldserver/worldserver.conf.dist
@@ -34,6 +34,7 @@
# AUCTION HOUSE BOT BUYER CONFIG
# LOGGING SYSTEM SETTINGS
# PACKET SPOOF PROTECTION SETTINGS
+# METRIC SETTINGS
#
###################################################################################################
@@ -3700,3 +3701,41 @@ PacketSpoof.BanDuration = 86400
#
###################################################################################################
+
+###################################################################################################
+# METRIC SETTINGS
+#
+# These settings control the statistics sent to the metric database (currently InfluxDB)
+#
+# Metric.Enable
+# Description: Enables statistics sent to the metric database.
+# Default: 0 - (Disabled)
+# 1 - (Enabled)
+
+Metric.Enable = 0
+
+#
+# Metric.Interval
+# Description: Interval between every batch of data sent in seconds
+# Default: 10 seconds
+#
+
+Metric.Interval = 10
+
+#
+# Metric.ConnectionInfo
+# Description: Connection settings for metric database (currently InfluxDB).
+# Example: "hostname;port;database"
+# Default: "127.0.0.1;8086;worldserver"
+
+Metric.ConnectionInfo = "127.0.0.1;8086;worldserver"
+
+#
+# Metric.OverallStatusInterval
+# Description: Interval between every gathering of overall worldserver status data in seconds
+# Default: 1 second
+#
+
+Metric.OverallStatusInterval = 1
+
+###################################################################################################