feat(Core/Metrics): implement real time statistic visualization (#8663)

This commit is contained in:
Kargatum 2021-10-28 19:47:29 +07:00 committed by GitHub
parent 7c363c9040
commit a650fd495c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 4798 additions and 31 deletions

1319
apps/grafana/1_General.json Normal file

File diff suppressed because it is too large Load Diff

691
apps/grafana/2_Maps.json Normal file
View File

@ -0,0 +1,691 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
},
{
"datasource": "Influx",
"enable": true,
"iconColor": "#C0C6BE",
"iconSize": 13,
"lineColor": "rgba(255, 96, 96, 0.592157)",
"name": "Global Events",
"query": "select title, text from events where $timeFilter and realm =~ /$realm$/",
"showLine": true,
"textColumn": "text",
"titleColumn": "title"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 6,
"iteration": 1595939001794,
"links": [],
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Influx",
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"grid": {},
"gridPos": {
"h": 7,
"w": 24,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 2,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [
{
"alias": "Unload tile",
"transform": "negative-Y"
}
],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "Load tile",
"dsType": "influxdb",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"0"
],
"type": "fill"
}
],
"query": "SELECT count(\"title\") FROM \"map_events\" WHERE \"realm\" =~ /$realm$/ AND \"title\" = 'LoadMapTile' AND $timeFilter GROUP BY time($interval) fill(0)",
"rawQuery": true,
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": []
},
{
"alias": "Unload tile",
"dsType": "influxdb",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"query": "SELECT count(\"title\") FROM \"map_events\" WHERE \"realm\" =~ /$realm$/ AND \"title\" = 'UnloadMapTile' AND $timeFilter GROUP BY time($interval) fill(0)",
"rawQuery": true,
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": []
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Map",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Influx",
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"grid": {},
"gridPos": {
"h": 7,
"w": 24,
"x": 0,
"y": 7
},
"hiddenSeries": false,
"id": 1,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "Pathfinding queries",
"dsType": "influxdb",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"query": "SELECT count(\"title\") FROM \"mmap_events\" WHERE \"realm\" =~ /$realm$/ AND \"title\" = 'CalculatePath' AND $timeFilter GROUP BY time($interval) fill(0)",
"rawQuery": true,
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": []
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "MMap",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 5,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 14
},
"hiddenSeries": false,
"id": 4,
"legend": {
"avg": false,
"current": false,
"hideEmpty": false,
"hideZero": true,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "Map $tag_map_id Instance $tag_map_instanceid",
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"map_id"
],
"type": "tag"
},
{
"params": [
"map_instanceid"
],
"type": "tag"
},
{
"params": [
"none"
],
"type": "fill"
}
],
"measurement": "map_creatures",
"orderByTime": "ASC",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": [
{
"key": "realm",
"operator": "=~",
"value": "/^$realm$/"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Creatures",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 5,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 22
},
"hiddenSeries": false,
"id": 5,
"legend": {
"avg": false,
"current": false,
"hideEmpty": false,
"hideZero": true,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "Map $tag_map_id Instance $tag_map_instanceid",
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"map_id"
],
"type": "tag"
},
{
"params": [
"map_instanceid"
],
"type": "tag"
},
{
"params": [
"none"
],
"type": "fill"
}
],
"measurement": "map_gameobjects",
"orderByTime": "ASC",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": [
{
"key": "realm",
"operator": "=~",
"value": "/^$realm$/"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Gameobjects",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "1m",
"schemaVersion": 25,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"allFormat": "regex values",
"allValue": null,
"current": {
"text": "Acore",
"value": "Acore"
},
"datasource": "Influx",
"definition": "",
"hide": 0,
"includeAll": false,
"label": null,
"multi": false,
"multiFormat": "regex values",
"name": "realm",
"options": [],
"query": "show tag values from events with key = realm",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "browser",
"title": "Maps, vmaps and mmaps",
"uid": "6IhqWiWGz",
"version": 2
}

280
apps/grafana/3_Network.json Normal file
View File

@ -0,0 +1,280 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
},
{
"datasource": "Influx",
"enable": true,
"iconColor": "#C0C6BE",
"iconSize": 13,
"lineColor": "rgba(255, 96, 96, 0.592157)",
"name": "Global Events",
"query": "select title, text from events where $timeFilter and realm =~ /$realm$/",
"showLine": true,
"textColumn": "text",
"titleColumn": "title"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 7,
"iteration": 1595939048589,
"links": [],
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Influx",
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"grid": {},
"gridPos": {
"h": 7,
"w": 24,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 1,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "Processed packets",
"dsType": "influxdb",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"0"
],
"type": "fill"
}
],
"measurement": "processed_packets",
"query": "SELECT sum(\"value\") FROM \"processed_packets\" WHERE \"realm\" =~ /$realm$/ AND $timeFilter GROUP BY time($interval) fill(0)",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "sum"
}
]
],
"tags": [
{
"key": "realm",
"operator": "=~",
"value": "/$realm$/"
}
]
},
{
"alias": "Processed packets / mean per session",
"dsType": "influxdb",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"0"
],
"type": "fill"
}
],
"measurement": "processed_packets",
"query": "SELECT mean(\"value\") FROM \"processed_packets\" WHERE \"realm\" =~ /$realm$/ AND $timeFilter GROUP BY time($interval) fill(0)",
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": [
{
"key": "realm",
"operator": "=~",
"value": "/$realm$/"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Processed packets",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "1m",
"schemaVersion": 25,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"allFormat": "regex values",
"allValue": null,
"current": {
"text": "Acore",
"value": "Acore"
},
"datasource": "Influx",
"definition": "",
"hide": 0,
"includeAll": false,
"label": null,
"multi": false,
"multiFormat": "regex values",
"name": "realm",
"options": [],
"query": "show tag values from events with key = realm",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "browser",
"title": "Network",
"uid": "_QtkMmWMk",
"version": 2
}

File diff suppressed because it is too large Load Diff

View File

@ -67,6 +67,8 @@ option(WITHOUT_GIT "Disable the GIT testing routines"
option(ENABLE_VMAP_CHECKS "Enable Checks relative to DisableMgr system on vmap" 1)
option(WITH_DYNAMIC_LINKING "Enable dynamic library linking." 0)
option(WITH_STRICT_DATABASE_TYPE_CHECKS "Enable strict checking of database field value accessors" 0)
option(WITHOUT_METRICS "Disable metrics reporting (i.e. InfluxDB and Grafana)" 0)
option(WITH_DETAILED_METRICS "Enable detailed metrics reporting (i.e. time each session takes to update)" 0)
IsDynamicLinkingRequired(WITH_DYNAMIC_LINKING_FORCED)
IsDynamicLinkingModulesRequired(WITH_DYNAMIC_LINKING_FORCED)

View File

@ -156,6 +156,18 @@ if(WITH_STRICT_DATABASE_TYPE_CHECKS)
add_definitions(-DACORE_STRICT_DATABASE_TYPE_CHECKS)
endif()
if(WITHOUT_METRICS)
message("")
message(" *** WITHOUT_METRICS - WARNING!")
message(" *** Please note that this will disable all metrics output (i.e. InfluxDB and Grafana)")
add_definitions(-DWITHOUT_METRICS)
elseif (WITH_DETAILED_METRICS)
message("")
message(" *** WITH_DETAILED_METRICS - WARNING!")
message(" *** Please note that this will enable detailed metrics output (i.e. time each session takes to update)")
add_definitions(-DWITH_DETAILED_METRICS)
endif()
if(BUILD_SHARED_LIBS)
message("")
message(" *** WITH_DYNAMIC_LINKING - INFO!")

View File

@ -18,6 +18,7 @@
#include "MapTree.h"
#include "Errors.h"
#include "Log.h"
#include "Metric.h"
#include "ModelInstance.h"
#include "VMapDefinitions.h"
#include "VMapMgr2.h"
@ -452,6 +453,10 @@ namespace VMAP
{
iLoadedTiles[packTileID(tileX, tileY)] = false;
}
METRIC_EVENT("map_events", "LoadMapTile",
"Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
return result;
}
@ -518,6 +523,9 @@ namespace VMAP
}
}
iLoadedTiles.erase(tile);
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)

View File

@ -0,0 +1,343 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 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 Affero 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 "Common.h"
#include "Config.h"
#include "DeadlineTimer.h"
#include "Log.h"
#include "Strand.h"
#include "Util.h"
#include "Tokenize.h"
#include <boost/algorithm/string/replace.hpp>
#include <boost/asio/ip/tcp.hpp>
Metric::Metric()
{
}
Metric::~Metric()
{
}
Metric* Metric::instance()
{
static Metric instance;
return &instance;
}
void Metric::Initialize(std::string const& realmName, Acore::Asio::IoContext& ioContext, std::function<void()> overallStatusLogger)
{
_dataStream = std::make_unique<boost::asio::ip::tcp::iostream>();
_realmName = FormatInfluxDBTagValue(realmName);
_batchTimer = std::make_unique<Acore::Asio::DeadlineTimer>(ioContext);
_overallStatusTimer = std::make_unique<Acore::Asio::DeadlineTimer>(ioContext);
_overallStatusLogger = overallStatusLogger;
LoadFromConfigs();
}
bool Metric::Connect()
{
auto& stream = static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream());
stream.connect(_hostname, _port);
auto error = stream.error();
if (error)
{
FMT_LOG_ERROR("metric", "Error connecting to '{}:{}', disabling Metric. Error message: {}",
_hostname, _port, error.message());
_enabled = false;
return false;
}
stream.clear();
return true;
}
void Metric::LoadFromConfigs()
{
bool previousValue = _enabled;
_enabled = sConfigMgr->GetOption<bool>("Metric.Enable", false);
_updateInterval = sConfigMgr->GetOption<int32>("Metric.Interval", 10);
if (_updateInterval < 1)
{
FMT_LOG_ERROR("metric", "'Metric.Interval' config set to {}, overriding to 1.", _updateInterval);
_updateInterval = 1;
}
_overallStatusTimerInterval = sConfigMgr->GetOption<int32>("Metric.OverallStatusInterval", 1);
if (_overallStatusTimerInterval < 1)
{
FMT_LOG_ERROR("metric", "'Metric.OverallStatusInterval' config set to {}, overriding to 1.", _overallStatusTimerInterval);
_overallStatusTimerInterval = 1;
}
_thresholds.clear();
std::vector<std::string> thresholdSettings = sConfigMgr->GetKeysByString("Metric.Threshold.");
for (std::string const& thresholdSetting : thresholdSettings)
{
int64 thresholdValue = sConfigMgr->GetOption<int64>(thresholdSetting, 0);
std::string thresholdName = thresholdSetting.substr(strlen("Metric.Threshold."));
_thresholds[thresholdName] = thresholdValue;
}
// 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->GetOption<std::string>("Metric.ConnectionInfo", "");
if (connectionInfo.empty())
{
FMT_LOG_ERROR("metric", "'Metric.ConnectionInfo' not specified in configuration file.");
return;
}
std::vector<std::string_view> tokens = Acore::Tokenize(connectionInfo, ';', true);
if (tokens.size() != 3)
{
FMT_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();
}
}
bool Metric::ShouldLog(std::string const& category, int64 value) const
{
auto threshold = _thresholds.find(category);
if (threshold == _thresholds.end())
{
return false;
}
return value >= threshold->second;
}
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;
for (MetricTag const& tag : data->Tags)
batchedData << "," << tag.first << "=" << FormatInfluxDBTagValue(tag.second);
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 << " " << 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 (!GetDataStream().good() && !Connect())
return;
GetDataStream() << "POST " << "/write?db=" << _databaseName << " HTTP/1.1\r\n";
GetDataStream() << "Host: " << _hostname << ":" << _port << "\r\n";
GetDataStream() << "Accept: */*\r\n";
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() << batchedData.rdbuf();
std::string http_version;
GetDataStream() >> http_version;
unsigned int status_code = 0;
GetDataStream() >> status_code;
if (status_code != 204)
{
FMT_LOG_ERROR("metric", "Error sending data, returned HTTP code: {}", status_code);
}
// Read and ignore the status description
std::string status_description;
std::getline(GetDataStream(), status_description);
// Read headers
std::string header;
while (std::getline(GetDataStream(), header) && header != "\r")
{
if (header == "Connection: close\r")
{
static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream()).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
{
static_cast<boost::asio::ip::tcp::iostream&>(GetDataStream()).close();
MetricData* data;
// Clear the queue
while (_queuedData.Dequeue(data))
{
delete data;
}
}
}
void Metric::Unload()
{
// Send what's queued only if IoContext is stopped (so only on shutdown)
if (_enabled && Acore::Asio::get_io_context(*_batchTimer).stopped())
{
_enabled = false;
SendBatch();
}
_batchTimer->cancel();
_overallStatusTimer->cancel();
}
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();
});
}
}
std::string Metric::FormatInfluxDBValue(bool value)
{
return value ? "t" : "f";
}
template<class T>
std::string Metric::FormatInfluxDBValue(T value)
{
return std::to_string(value) + 'i';
}
std::string Metric::FormatInfluxDBValue(std::string const& value)
{
return '"' + boost::replace_all_copy(value, "\"", "\\\"") + '"';
}
std::string Metric::FormatInfluxDBValue(char const* value)
{
return FormatInfluxDBValue(std::string(value));
}
std::string Metric::FormatInfluxDBValue(double value)
{
return std::to_string(value);
}
std::string Metric::FormatInfluxDBValue(float value)
{
return FormatInfluxDBValue(double(value));
}
std::string Metric::FormatInfluxDBTagValue(std::string const& value)
{
// ToDo: should handle '=' and ',' characters too
return boost::replace_all_copy(value, " ", "\\ ");
}
std::string Metric::FormatInfluxDBValue(std::chrono::nanoseconds value)
{
return FormatInfluxDBValue(std::chrono::duration_cast<Milliseconds>(value).count());
}
template AC_COMMON_API std::string Metric::FormatInfluxDBValue(int8);
template AC_COMMON_API std::string Metric::FormatInfluxDBValue(uint8);
template AC_COMMON_API std::string Metric::FormatInfluxDBValue(int16);
template AC_COMMON_API std::string Metric::FormatInfluxDBValue(uint16);
template AC_COMMON_API std::string Metric::FormatInfluxDBValue(int32);
template AC_COMMON_API std::string Metric::FormatInfluxDBValue(uint32);
template AC_COMMON_API std::string Metric::FormatInfluxDBValue(int64);
template AC_COMMON_API std::string Metric::FormatInfluxDBValue(uint64);

227
src/common/Metric/Metric.h Normal file
View File

@ -0,0 +1,227 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 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 Affero 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 "Define.h"
#include "Duration.h"
#include "MPSCQueue.h"
#include <functional>
#include <iosfwd>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <utility>
namespace Acore::Asio
{
class IoContext;
class DeadlineTimer;
}
enum MetricDataType
{
METRIC_DATA_VALUE,
METRIC_DATA_EVENT
};
typedef std::pair<std::string, std::string> MetricTag;
struct MetricData
{
std::string Category;
SystemTimePoint Timestamp;
MetricDataType Type;
std::vector<MetricTag> Tags;
// LogValue-specific fields
std::string Value;
// LogEvent-specific fields
std::string Title;
std::string Text;
};
class AC_COMMON_API Metric
{
private:
std::iostream& GetDataStream() { return *_dataStream; }
std::unique_ptr<std::iostream> _dataStream;
MPSCQueue<MetricData> _queuedData;
std::unique_ptr<Acore::Asio::DeadlineTimer> _batchTimer;
std::unique_ptr<Acore::Asio::DeadlineTimer> _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;
std::unordered_map<std::string, int64> _thresholds;
bool Connect();
void SendBatch();
void ScheduleSend();
void ScheduleOverallStatusLog();
static std::string FormatInfluxDBValue(bool value);
template <class T>
static std::string FormatInfluxDBValue(T value);
static std::string FormatInfluxDBValue(std::string const& value);
static std::string FormatInfluxDBValue(char const* value);
static std::string FormatInfluxDBValue(double value);
static std::string FormatInfluxDBValue(float value);
static std::string FormatInfluxDBValue(std::chrono::nanoseconds value);
static std::string FormatInfluxDBTagValue(std::string const& value);
// ToDo: should format TagKey and FieldKey too in the same way as TagValue
public:
Metric();
~Metric();
static Metric* instance();
void Initialize(std::string const& realmName, Acore::Asio::IoContext& ioContext, std::function<void()> overallStatusLogger);
void LoadFromConfigs();
void Update();
bool ShouldLog(std::string const& category, int64 value) const;
template<class T>
void LogValue(std::string const& category, T value, std::vector<MetricTag> tags)
{
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);
data->Tags = std::move(tags);
_queuedData.Enqueue(data);
}
void LogEvent(std::string const& category, std::string const& title, std::string const& description);
void Unload();
bool IsEnabled() const { return _enabled; }
};
#define sMetric Metric::instance()
template<typename LoggerType>
class MetricStopWatch
{
public:
MetricStopWatch(LoggerType&& loggerFunc) :
_logger(std::forward<LoggerType>(loggerFunc)),
_startTime(sMetric->IsEnabled() ? std::chrono::steady_clock::now() : TimePoint())
{
}
~MetricStopWatch()
{
if (sMetric->IsEnabled())
_logger(_startTime);
}
private:
LoggerType _logger;
TimePoint _startTime;
};
template<typename LoggerType>
MetricStopWatch<LoggerType> MakeMetricStopWatch(LoggerType&& loggerFunc)
{
return { std::forward<LoggerType>(loggerFunc) };
}
#define METRIC_TAG(name, value) { name, value }
#define METRIC_DO_CONCAT(a, b) a##b
#define METRIC_CONCAT(a, b) METRIC_DO_CONCAT(a, b)
#define METRIC_UNIQUE_NAME(name) METRIC_CONCAT(name, __LINE__)
#if defined PERFORMANCE_PROFILING || defined WITHOUT_METRICS
#define METRIC_EVENT(category, title, description) ((void)0)
#define METRIC_VALUE(category, value, ...) ((void)0)
#define METRIC_TIMER(category, ...) ((void)0)
#define METRIC_DETAILED_EVENT(category, title, description) ((void)0)
#define METRIC_DETAILED_TIMER(category, ...) ((void)0)
#define METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) ((void)0)
#else
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
#define METRIC_EVENT(category, title, description) \
do { \
if (sMetric->IsEnabled()) \
sMetric->LogEvent(category, title, description); \
} while (0)
#define METRIC_VALUE(category, value, ...) \
do { \
if (sMetric->IsEnabled()) \
sMetric->LogValue(category, value, { __VA_ARGS__ }); \
} while (0)
#else
#define 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 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 METRIC_TIMER(category, ...) \
MetricStopWatch METRIC_UNIQUE_NAME(__ac_metric_stop_watch) = MakeMetricStopWatch([&](TimePoint start) \
{ \
sMetric->LogValue(category, std::chrono::steady_clock::now() - start, { __VA_ARGS__ }); \
});
#if defined WITH_DETAILED_METRICS
#define METRIC_DETAILED_TIMER(category, ...) \
MetricStopWatch METRIC_UNIQUE_NAME(__ac_metric_stop_watch) = MakeMetricStopWatch([&](TimePoint start) \
{ \
int64 duration = int64(std::chrono::duration_cast<Milliseconds>(std::chrono::steady_clock::now() - start).count()); \
if (sMetric->ShouldLog(category, duration)) \
sMetric->LogValue(category, duration, { __VA_ARGS__ }); \
});
#define METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) METRIC_TIMER(category, __VA_ARGS__)
#define METRIC_DETAILED_EVENT(category, title, description) METRIC_EVENT(category, title, description)
#else
#define METRIC_DETAILED_EVENT(category, title, description) ((void)0)
#define METRIC_DETAILED_TIMER(category, ...) ((void)0)
#define METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) ((void)0)
#endif
#endif
#endif // METRIC_H__

View File

@ -51,6 +51,11 @@ public:
return _queue.empty();
}
size_t Size() const
{
return _queue.size();
}
bool Pop(T& value)
{
std::lock_guard<std::mutex> lock(_queueLock);

View File

@ -430,6 +430,12 @@ void DatabaseWorkerPool<T>::Enqueue(SQLOperation* op)
_queue->Push(op);
}
template <class T>
size_t DatabaseWorkerPool<T>::QueueSize() const
{
return _queue->Size();
}
template <class T>
T* DatabaseWorkerPool<T>::GetFreeConnection()
{

View File

@ -213,6 +213,8 @@ public:
#endif
}
size_t QueueSize() const;
private:
uint32 OpenConnections(InternalIndex type, uint8 numConnections);

View File

@ -29,12 +29,14 @@
#include "GuildMgr.h"
#include "Language.h"
#include "Log.h"
#include "Metric.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "Pet.h"
#include "Player.h"
#include "PlayerDump.h"
#include "QueryHolder.h"
#include "Realm.h"
#include "ReputationMgr.h"
#include "ScriptMgr.h"
@ -43,15 +45,14 @@
#include "SocialMgr.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "StringConvert.h"
#include "Tokenize.h"
#include "Transport.h"
#include "UpdateMask.h"
#include "Util.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "QueryHolder.h"
#include "StringConvert.h"
#include "Tokenize.h"
#ifdef ELUNA
#include "LuaEngine.h"
@ -1087,6 +1088,8 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder)
pCurrChar->RemoveAtLoginFlag(AT_LOGIN_FIRST);
sScriptMgr->OnFirstLogin(pCurrChar);
}
METRIC_EVENT("player_events", "Login", pCurrChar->GetName());
}
void WorldSession::HandlePlayerLoginToCharInWorld(Player* pCurrChar)

View File

@ -28,6 +28,7 @@
#include "LFGMgr.h"
#include "Map.h"
#include "MapInstanced.h"
#include "Metric.h"
#include "Object.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
@ -868,6 +869,14 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
HandleDelayedVisibility();
sScriptMgr->OnMapUpdate(this, t_diff);
METRIC_VALUE("map_creatures", uint64(GetObjectsStore().Size<Creature>()),
METRIC_TAG("map_id", std::to_string(GetId())),
METRIC_TAG("map_instanceid", std::to_string(GetInstanceId())));
METRIC_VALUE("map_gameobjects", uint64(GetObjectsStore().Size<GameObject>()),
METRIC_TAG("map_id", std::to_string(GetId())),
METRIC_TAG("map_instanceid", std::to_string(GetInstanceId())));
}
void Map::HandleDelayedVisibility()

View File

@ -19,6 +19,7 @@
#include "LFGMgr.h"
#include "Map.h"
#include "MapUpdater.h"
#include "Metric.h"
class UpdateRequest
{
@ -39,9 +40,11 @@ public:
void call() override
{
METRIC_TIMER("map_update_time_diff", METRIC_TAG("map_id", std::to_string(m_map.GetId())));
m_map.Update(m_diff, s_diff);
m_updater.update_finished();
}
private:
Map& m_map;
MapUpdater& m_updater;

View File

@ -20,6 +20,7 @@
#include "Geometry.h"
#include "Log.h"
#include "Map.h"
#include "Metric.h"
#include "MMapFactory.h"
#include "MMapMgr.h"
#include "PathGenerator.h"
@ -61,6 +62,8 @@ bool PathGenerator::CalculatePath(float x, float y, float z, float destX, float
if (!Acore::IsValidMapCoord(destX, destY, destZ) || !Acore::IsValidMapCoord(x, y, z))
return false;
METRIC_DETAILED_EVENT("mmap_events", "CalculatePath", "");
G3D::Vector3 dest(destX, destY, destZ);
SetEndPosition(dest);

View File

@ -30,6 +30,7 @@
#include "Hyperlinks.h"
#include "Log.h"
#include "MapMgr.h"
#include "Metric.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
@ -319,6 +320,8 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode());
ClientOpcodeHandler const* opHandle = opcodeTable[opcode];
METRIC_DETAILED_TIMER("worldsession_update_opcode_time", METRIC_TAG("opcode", opHandle->Name));
try
{
switch (opHandle->Status)
@ -441,6 +444,8 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
_recvQueue.readd(requeuePackets.begin(), requeuePackets.end());
METRIC_VALUE("processed_packets", processedPackets);
if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter
{
// Send time sync packet every 10s.
@ -671,6 +676,8 @@ void WorldSession::LogoutPlayer(bool save)
//! Call script hook before deletion
sScriptMgr->OnPlayerLogout(_player);
METRIC_EVENT("player_events", "Logout", _player->GetName());
LOG_INFO("entities.player", "Account: %d (IP: %s) Logout Character:[%s] (%s) Level: %d",
GetAccountId(), GetRemoteAddress().c_str(), _player->GetName().c_str(), _player->GetGUID().ToString().c_str(), _player->getLevel());

View File

@ -60,6 +60,7 @@
#include "LootMgr.h"
#include "MMapFactory.h"
#include "MapMgr.h"
#include "Metric.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "OutdoorPvPMgr.h"
@ -437,6 +438,7 @@ void World::LoadConfigSettings(bool reload)
}
sLog->LoadFromConfig();
sMetric->LoadFromConfigs();
}
// Set realm id and enable db logging
@ -2107,10 +2109,13 @@ void World::SetInitialWorldSettings()
}
uint32 startupDuration = GetMSTimeDiffToNow(startupBegin);
LOG_INFO("server.loading", " ");
LOG_INFO("server.loading", "WORLD: World initialized in %u minutes %u seconds", (startupDuration / 60000), ((startupDuration % 60000) / 1000)); // outError for red color in console
LOG_INFO("server.loading", " ");
METRIC_EVENT("events", "World initialized", "World initialized in " + std::to_string(startupDuration / 60000) + " minutes " + std::to_string((startupDuration % 60000) / 1000) + " seconds");
if (sConfigMgr->isDryRun())
{
LOG_INFO("server.loading", "AzerothCore dry run completed, terminating.");
@ -2199,6 +2204,8 @@ void World::LoadAutobroadcasts()
/// Update the World !
void World::Update(uint32 diff)
{
METRIC_TIMER("world_update_time_total");
m_updateTime = diff;
if (m_int_configs[CONFIG_INTERVAL_LOG_UPDATE])
@ -2238,26 +2245,45 @@ void World::Update(uint32 diff)
///- Update the game time and check for shutdown time
_UpdateGameTime();
/// Handle daily quests reset time
if (m_gameTime > m_NextDailyQuestReset)
ResetDailyQuests();
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Check quest reset times"));
/// Handle weekly quests reset time
if (m_gameTime > m_NextWeeklyQuestReset)
ResetWeeklyQuests();
/// Handle daily quests reset time
if (m_gameTime > m_NextDailyQuestReset)
{
ResetDailyQuests();
}
/// Handle monthly quests reset time
if (m_gameTime > m_NextMonthlyQuestReset)
ResetMonthlyQuests();
/// Handle weekly quests reset time
if (m_gameTime > m_NextWeeklyQuestReset)
{
ResetWeeklyQuests();
}
/// Handle monthly quests reset time
if (m_gameTime > m_NextMonthlyQuestReset)
{
ResetMonthlyQuests();
}
}
if (m_gameTime > m_NextRandomBGReset)
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Reset random BG"));
ResetRandomBG();
}
if (m_gameTime > m_NextCalendarOldEventsDeletionTime)
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Delete old calendar events"));
CalendarDeleteOldEvents();
}
if (m_gameTime > m_NextGuildReset)
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Reset guild cap"));
ResetGuildCap();
}
// pussywizard:
// acquire mutex now, this is kind of waiting for listing thread to finish it's work (since it can't process next packet)
@ -2269,6 +2295,8 @@ void World::Update(uint32 diff)
// pussywizard: handle auctions when the timer has passed
if (m_timers[WUPDATE_AUCTIONS].Passed())
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update expired auctions"));
m_timers[WUPDATE_AUCTIONS].Reset();
// pussywizard: handle expired auctions, auctions expired when realm was offline are also handled here (not during loading when many required things aren't loaded yet)
@ -2283,8 +2311,13 @@ void World::Update(uint32 diff)
mail_expire_check_timer = m_gameTime + 6 * 3600;
}
UpdateSessions(diff);
{
/// <li> Handle session updates when the timer has passed
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update sessions"));
UpdateSessions(diff);
}
}
// end of section with mutex
AsyncAuctionListingMgr::SetAuctionListingAllowed(true);
@ -2300,6 +2333,8 @@ void World::Update(uint32 diff)
{
if (m_timers[WUPDATE_CLEANDB].Passed())
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Clean logs table"));
m_timers[WUPDATE_CLEANDB].Reset();
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_OLD_LOGS);
@ -2311,33 +2346,58 @@ void World::Update(uint32 diff)
}
}
sLFGMgr->Update(diff, 0); // pussywizard: remove obsolete stuff before finding compatibility during map update
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update LFG 0"));
sLFGMgr->Update(diff, 0); // pussywizard: remove obsolete stuff before finding compatibility during map update
}
sMapMgr->Update(diff);
///- Update objects when the timer has passed (maps, transport, creatures, ...)
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update maps"));
sMapMgr->Update(diff);
}
if (sWorld->getBoolConfig(CONFIG_AUTOBROADCAST))
{
if (m_timers[WUPDATE_AUTOBROADCAST].Passed())
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Send autobroadcast"));
m_timers[WUPDATE_AUTOBROADCAST].Reset();
SendAutoBroadcast();
}
}
sBattlegroundMgr->Update(diff);
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update battlegrounds"));
sBattlegroundMgr->Update(diff);
}
sOutdoorPvPMgr->Update(diff);
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update outdoor pvp"));
sOutdoorPvPMgr->Update(diff);
}
sBattlefieldMgr->Update(diff);
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update battlefields"));
sBattlefieldMgr->Update(diff);
}
sLFGMgr->Update(diff, 2); // pussywizard: handle created proposals
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update LFG 2"));
sLFGMgr->Update(diff, 2); // pussywizard: handle created proposals
}
// execute callbacks from sql queries that were queued recently
ProcessQueryCallbacks();
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Process query callbacks"));
// execute callbacks from sql queries that were queued recently
ProcessQueryCallbacks();
}
/// <li> Update uptime table
if (m_timers[WUPDATE_UPTIME].Passed())
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update uptime"));
uint32 tmpDiff = uint32(m_gameTime - m_startTime);
uint32 maxOnlinePlayers = GetMaxPlayerCount();
@ -2356,6 +2416,7 @@ void World::Update(uint32 diff)
///- Erase corpses once every 20 minutes
if (m_timers[WUPDATE_CORPSES].Passed())
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Remove old corpses"));
m_timers[WUPDATE_CORPSES].Reset();
sMapMgr->DoForAllMaps([](Map* map)
{
@ -2366,6 +2427,7 @@ void World::Update(uint32 diff)
///- Process Game events when necessary
if (m_timers[WUPDATE_EVENTS].Passed())
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update game events"));
m_timers[WUPDATE_EVENTS].Reset(); // to give time for Update() to be processed
uint32 nextGameEvent = sGameEventMgr->Update();
m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent);
@ -2375,6 +2437,7 @@ void World::Update(uint32 diff)
///- Ping to keep MySQL connections alive
if (m_timers[WUPDATE_PINGDB].Passed())
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Ping MySQL"));
m_timers[WUPDATE_PINGDB].Reset();
LOG_DEBUG("sql.driver", "Ping MySQL to keep connection alive");
CharacterDatabase.KeepAlive();
@ -2382,15 +2445,34 @@ void World::Update(uint32 diff)
WorldDatabase.KeepAlive();
}
// update the instance reset times
sInstanceSaveMgr->Update();
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update instance reset times"));
// update the instance reset times
sInstanceSaveMgr->Update();
}
// And last, but not least handle the issued cli commands
ProcessCliCommands();
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Process cli commands"));
// And last, but not least handle the issued cli commands
ProcessCliCommands();
}
sScriptMgr->OnWorldUpdate(diff);
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update world scripts"));
sScriptMgr->OnWorldUpdate(diff);
}
SavingSystemMgr::Update(diff);
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update saving system"));
SavingSystemMgr::Update(diff);
}
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update metrics"));
// Stats logger update
sMetric->Update();
METRIC_VALUE("update_time_diff", diff);
}
}
void World::ForceGameEventUpdate()
@ -2710,10 +2792,18 @@ void World::SendServerMessage(ServerMessageType type, const char* text, Player*
void World::UpdateSessions(uint32 diff)
{
///- Add new sessions
WorldSession* sess = nullptr;
while (addSessQueue.next(sess))
AddSession_ (sess);
{
METRIC_DETAILED_NO_THRESHOLD_TIMER("world_update_time",
METRIC_TAG("type", "Add sessions"),
METRIC_TAG("parent_type", "Update sessions"));
///- Add new sessions
WorldSession* sess = nullptr;
while (addSessQueue.next(sess))
{
AddSession_(sess);
}
}
///- Then send an update signal to remaining ones
for (SessionMap::iterator itr = m_sessions.begin(), next; itr != m_sessions.end(); itr = next)
@ -2745,6 +2835,9 @@ void World::UpdateSessions(uint32 diff)
continue;
}
[[maybe_unused]] uint32 currentSessionId = itr->first;
METRIC_DETAILED_TIMER("world_update_sessions_time", METRIC_TAG("account_id", std::to_string(currentSessionId)));
if (!pSession->Update(diff, updater))
{
if (!RemoveQueuedPlayer(pSession) && getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))

View File

@ -219,6 +219,10 @@ public:
handler->PSendSysMessage("Using World DB Revision: %s", sWorld->GetWorldDBRevision());
handler->PSendSysMessage("Using Character DB Revision: %s", sWorld->GetCharacterDBRevision());
handler->PSendSysMessage("Using Auth DB Revision: %s", sWorld->GetAuthDBRevision());
handler->PSendSysMessage("LoginDatabase queue size: %zu", LoginDatabase.QueueSize());
handler->PSendSysMessage("CharacterDatabase queue size: %zu", CharacterDatabase.QueueSize());
handler->PSendSysMessage("WorldDatabase queue size: %zu", WorldDatabase.QueueSize());
return true;
}

View File

@ -37,6 +37,7 @@
#include "GitRevision.h"
#include "IoContext.h"
#include "MapMgr.h"
#include "Metric.h"
#include "MySQLThreading.h"
#include "ObjectAccessor.h"
#include "OpenSSLCrypto.h"
@ -288,6 +289,22 @@ int main(int argc, char** argv)
LoadRealmInfo(*ioContext);
sMetric->Initialize(realm.Name, *ioContext, []()
{
METRIC_VALUE("online_players", sWorld->GetPlayerCount());
METRIC_VALUE("db_queue_login", uint64(LoginDatabase.QueueSize()));
METRIC_VALUE("db_queue_character", uint64(CharacterDatabase.QueueSize()));
METRIC_VALUE("db_queue_world", uint64(WorldDatabase.QueueSize()));
});
METRIC_EVENT("events", "Worldserver started", "");
std::shared_ptr<void> sMetricHandle(nullptr, [](void*)
{
METRIC_EVENT("events", "Worldserver shutdown", "");
sMetric->Unload();
});
// Loading modules configs
sConfigMgr->PrintLoadedModulesConfigs();

View File

@ -31,6 +31,7 @@
# LOGGING SYSTEM SETTINGS
# PACKET SPOOF PROTECTION SETTINGS
# DEBUG
# METRIC SETTINGS
#
###################################################################################################
@ -3821,3 +3822,58 @@ Debug.Arena = 0
#
###################################################################################################
###################################################################################################
# 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
#
# Metric threshold values: Given a metric "name"
# Metric.Threshold.name
# Description: Skips sending statistics with a value lower than the config value.
# If the threshold is commented out, the metric will be ignored.
# Only metrics logged with METRIC_DETAILED_TIMER in the sources are affected.
# Disabled by default. Requires WITH_DETAILED_METRICS CMake flag.
#
# Format: Value as integer
#
#Metric.Threshold.world_update_sessions_time = 100
#Metric.Threshold.worldsession_update_opcode_time = 50
#
###################################################################################################