mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(database): server_traffic table for per-minute traffic rollups
One row per server and minute (packets, bytes, resends, HTTP requests, errors, bytes and latency percentiles), written in batches and read summed into buckets for the longer chart ranges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
#include "IPropertyRent.h"
|
||||
#include "IPropertyReputation.h"
|
||||
#include "IBbbAutosave.h"
|
||||
#include "IServerTraffic.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (std::exception& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0)
|
||||
@@ -64,7 +65,7 @@ class GameDatabase :
|
||||
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
|
||||
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
|
||||
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
|
||||
public IBehaviors, public IUgcModularBuild, public IAuditLog, public IDashboardStats, public IAccountEmails, public IDashboardMaintenance, public IEconomyLedger, public IDashboardAdmin, public IServerConfig, public IScheduledTasks, public ICharacterSnapshots, public IAccountNotes, public IServerHealth, public IRelatedData, public IChatLog, public IAccountStrikes, public ISlashCommands, public IModeration, public IServerOperations, public IPlayerPositions, public IAiSuggestions, public ILiveOps, public IFeaturedProperties, public IMessageCaptures, public IContraband, public IPropertyRent, public IPropertyReputation, public IBbbAutosave {
|
||||
public IBehaviors, public IUgcModularBuild, public IAuditLog, public IDashboardStats, public IAccountEmails, public IDashboardMaintenance, public IEconomyLedger, public IDashboardAdmin, public IServerConfig, public IScheduledTasks, public ICharacterSnapshots, public IAccountNotes, public IServerHealth, public IRelatedData, public IChatLog, public IAccountStrikes, public ISlashCommands, public IModeration, public IServerOperations, public IPlayerPositions, public IAiSuggestions, public ILiveOps, public IFeaturedProperties, public IMessageCaptures, public IContraband, public IPropertyRent, public IPropertyReputation, public IBbbAutosave, public IServerTraffic {
|
||||
public:
|
||||
virtual ~GameDatabase() = default;
|
||||
// TODO: These should be made private.
|
||||
|
||||
40
dDatabase/GameDatabase/ITables/IServerTraffic.h
Normal file
40
dDatabase/GameDatabase/ITables/IServerTraffic.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef __ISERVERTRAFFIC__H__
|
||||
#define __ISERVERTRAFFIC__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Per-minute traffic of each server (packets, bytes, HTTP requests), written by the dashboard once a minute from the
|
||||
* servers' traffic reports and kept for traffic_days. The last hour at one second resolution is only in memory.
|
||||
*/
|
||||
class IServerTraffic {
|
||||
public:
|
||||
struct TrafficMinute {
|
||||
int64_t time{}; // start of the minute (or bucket), Unix seconds
|
||||
std::string server; // "master", "auth", "chat", "dashboard", "ugc", "world:<zone>:<instance>"
|
||||
uint64_t packetsIn{};
|
||||
uint64_t packetsOut{};
|
||||
uint64_t bytesIn{};
|
||||
uint64_t bytesOut{};
|
||||
uint64_t resends{};
|
||||
uint64_t httpRequests{};
|
||||
uint64_t http4xx{};
|
||||
uint64_t http5xx{};
|
||||
uint64_t httpBytesOut{};
|
||||
uint32_t latencyP50Us{}; // HTTP; in a bucket of several minutes: the average of the minutes'
|
||||
uint32_t latencyP95Us{}; // in a bucket: the worst minute's
|
||||
uint32_t latencyP99Us{}; // in a bucket: the worst minute's
|
||||
};
|
||||
|
||||
// One batch a minute
|
||||
virtual void InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) = 0;
|
||||
|
||||
// Rows between from and to (inclusive), summed per server into buckets of `bucketSeconds`, ordered by time
|
||||
virtual std::vector<TrafficMinute> GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) = 0;
|
||||
|
||||
virtual uint32_t PruneTrafficMinutes(int64_t beforeTime) = 0;
|
||||
};
|
||||
|
||||
#endif //!__ISERVERTRAFFIC__H__
|
||||
@@ -215,6 +215,10 @@ public:
|
||||
std::vector<ZoneLimit> GetZoneLimits() override;
|
||||
void SetZoneLimit(const ZoneLimit& limit) override;
|
||||
void DeleteZoneLimit(uint32_t zoneId) override;
|
||||
// IServerTraffic
|
||||
void InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) override;
|
||||
std::vector<TrafficMinute> GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) override;
|
||||
uint32_t PruneTrafficMinutes(int64_t beforeTime) override;
|
||||
// IBbbAutosave
|
||||
std::optional<IBbbAutosave::Info> GetBbbAutosave(const LWOOBJID characterId) override;
|
||||
void SetBbbAutosave(const LWOOBJID characterId, const IBbbAutosave::Info& info) override;
|
||||
|
||||
@@ -22,6 +22,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES
|
||||
"PropertyRent.cpp"
|
||||
"PropertyReputation.cpp"
|
||||
"BbbAutosave.cpp"
|
||||
"ServerTraffic.cpp"
|
||||
"MessageCaptures.cpp"
|
||||
"ChatLog.cpp"
|
||||
"RelatedData.cpp"
|
||||
|
||||
46
dDatabase/GameDatabase/MySQL/Tables/ServerTraffic.cpp
Normal file
46
dDatabase/GameDatabase/MySQL/Tables/ServerTraffic.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
void MySQLDatabase::InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) {
|
||||
if (minutes.empty()) return;
|
||||
const auto prevCommit = GetAutoCommit();
|
||||
SetAutoCommit(false);
|
||||
for (const auto& m : minutes) {
|
||||
ExecuteInsert("REPLACE INTO server_traffic (time, server, packets_in, packets_out, bytes_in, bytes_out, resends, http_requests, http_4xx, http_5xx, http_bytes_out, "
|
||||
"latency_p50_us, latency_p95_us, latency_p99_us) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
m.time, m.server, m.packetsIn, m.packetsOut, m.bytesIn, m.bytesOut, m.resends, m.httpRequests, m.http4xx, m.http5xx, m.httpBytesOut,
|
||||
m.latencyP50Us, m.latencyP95Us, m.latencyP99Us);
|
||||
}
|
||||
Commit();
|
||||
SetAutoCommit(prevCommit);
|
||||
}
|
||||
|
||||
std::vector<IServerTraffic::TrafficMinute> MySQLDatabase::GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) {
|
||||
std::vector<TrafficMinute> minutes;
|
||||
auto result = ExecuteSelect("SELECT FLOOR(time / ?) * ? AS bucket, server, SUM(packets_in) AS packets_in, SUM(packets_out) AS packets_out, SUM(bytes_in) AS bytes_in, "
|
||||
"SUM(bytes_out) AS bytes_out, SUM(resends) AS resends, SUM(http_requests) AS http_requests, SUM(http_4xx) AS http_4xx, SUM(http_5xx) AS http_5xx, "
|
||||
"SUM(http_bytes_out) AS http_bytes_out, AVG(latency_p50_us) AS p50, MAX(latency_p95_us) AS p95, MAX(latency_p99_us) AS p99 "
|
||||
"FROM server_traffic WHERE time >= ? AND time <= ? GROUP BY bucket, server ORDER BY bucket, server;", bucketSeconds, bucketSeconds, from, to);
|
||||
while (result->next()) {
|
||||
TrafficMinute m;
|
||||
m.time = result->getInt64("bucket");
|
||||
m.server = result->getString("server").c_str();
|
||||
m.packetsIn = result->getUInt64("packets_in");
|
||||
m.packetsOut = result->getUInt64("packets_out");
|
||||
m.bytesIn = result->getUInt64("bytes_in");
|
||||
m.bytesOut = result->getUInt64("bytes_out");
|
||||
m.resends = result->getUInt64("resends");
|
||||
m.httpRequests = result->getUInt64("http_requests");
|
||||
m.http4xx = result->getUInt64("http_4xx");
|
||||
m.http5xx = result->getUInt64("http_5xx");
|
||||
m.httpBytesOut = result->getUInt64("http_bytes_out");
|
||||
m.latencyP50Us = static_cast<uint32_t>(result->getDouble("p50"));
|
||||
m.latencyP95Us = result->getUInt("p95");
|
||||
m.latencyP99Us = result->getUInt("p99");
|
||||
minutes.push_back(std::move(m));
|
||||
}
|
||||
return minutes;
|
||||
}
|
||||
|
||||
uint32_t MySQLDatabase::PruneTrafficMinutes(int64_t beforeTime) {
|
||||
return static_cast<uint32_t>(ExecuteUpdate("DELETE FROM server_traffic WHERE time < ?;", beforeTime));
|
||||
}
|
||||
@@ -199,6 +199,10 @@ public:
|
||||
std::vector<ZoneLimit> GetZoneLimits() override;
|
||||
void SetZoneLimit(const ZoneLimit& limit) override;
|
||||
void DeleteZoneLimit(uint32_t zoneId) override;
|
||||
// IServerTraffic
|
||||
void InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) override;
|
||||
std::vector<TrafficMinute> GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) override;
|
||||
uint32_t PruneTrafficMinutes(int64_t beforeTime) override;
|
||||
// IBbbAutosave
|
||||
std::optional<IBbbAutosave::Info> GetBbbAutosave(const LWOOBJID characterId) override;
|
||||
void SetBbbAutosave(const LWOOBJID characterId, const IBbbAutosave::Info& info) override;
|
||||
|
||||
@@ -22,6 +22,7 @@ set(DDATABASES_DATABASES_SQLITE_TABLES_SOURCES
|
||||
"PropertyRent.cpp"
|
||||
"PropertyReputation.cpp"
|
||||
"BbbAutosave.cpp"
|
||||
"ServerTraffic.cpp"
|
||||
"MessageCaptures.cpp"
|
||||
"ChatLog.cpp"
|
||||
"RelatedData.cpp"
|
||||
|
||||
46
dDatabase/GameDatabase/SQLite/Tables/ServerTraffic.cpp
Normal file
46
dDatabase/GameDatabase/SQLite/Tables/ServerTraffic.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) {
|
||||
if (minutes.empty()) return;
|
||||
const auto prevCommit = GetAutoCommit();
|
||||
SetAutoCommit(false);
|
||||
for (const auto& m : minutes) {
|
||||
ExecuteInsert("INSERT OR REPLACE INTO server_traffic (time, server, packets_in, packets_out, bytes_in, bytes_out, resends, http_requests, http_4xx, http_5xx, http_bytes_out, "
|
||||
"latency_p50_us, latency_p95_us, latency_p99_us) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
m.time, m.server, m.packetsIn, m.packetsOut, m.bytesIn, m.bytesOut, m.resends, m.httpRequests, m.http4xx, m.http5xx, m.httpBytesOut,
|
||||
m.latencyP50Us, m.latencyP95Us, m.latencyP99Us);
|
||||
}
|
||||
Commit();
|
||||
SetAutoCommit(prevCommit);
|
||||
}
|
||||
|
||||
std::vector<IServerTraffic::TrafficMinute> SQLiteDatabase::GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) {
|
||||
std::vector<TrafficMinute> minutes;
|
||||
auto [_, result] = ExecuteSelect("SELECT (time / ?) * ? AS bucket, server, SUM(packets_in) AS packets_in, SUM(packets_out) AS packets_out, SUM(bytes_in) AS bytes_in, "
|
||||
"SUM(bytes_out) AS bytes_out, SUM(resends) AS resends, SUM(http_requests) AS http_requests, SUM(http_4xx) AS http_4xx, SUM(http_5xx) AS http_5xx, "
|
||||
"SUM(http_bytes_out) AS http_bytes_out, AVG(latency_p50_us) AS p50, MAX(latency_p95_us) AS p95, MAX(latency_p99_us) AS p99 "
|
||||
"FROM server_traffic WHERE time >= ? AND time <= ? GROUP BY bucket, server ORDER BY bucket, server;", bucketSeconds, bucketSeconds, from, to);
|
||||
for (; !result.eof(); result.nextRow()) {
|
||||
TrafficMinute m;
|
||||
m.time = result.getInt64Field("bucket");
|
||||
m.server = result.getStringField("server");
|
||||
m.packetsIn = static_cast<uint64_t>(result.getInt64Field("packets_in"));
|
||||
m.packetsOut = static_cast<uint64_t>(result.getInt64Field("packets_out"));
|
||||
m.bytesIn = static_cast<uint64_t>(result.getInt64Field("bytes_in"));
|
||||
m.bytesOut = static_cast<uint64_t>(result.getInt64Field("bytes_out"));
|
||||
m.resends = static_cast<uint64_t>(result.getInt64Field("resends"));
|
||||
m.httpRequests = static_cast<uint64_t>(result.getInt64Field("http_requests"));
|
||||
m.http4xx = static_cast<uint64_t>(result.getInt64Field("http_4xx"));
|
||||
m.http5xx = static_cast<uint64_t>(result.getInt64Field("http_5xx"));
|
||||
m.httpBytesOut = static_cast<uint64_t>(result.getInt64Field("http_bytes_out"));
|
||||
m.latencyP50Us = static_cast<uint32_t>(result.getFloatField("p50"));
|
||||
m.latencyP95Us = static_cast<uint32_t>(result.getInt64Field("p95"));
|
||||
m.latencyP99Us = static_cast<uint32_t>(result.getInt64Field("p99"));
|
||||
minutes.push_back(std::move(m));
|
||||
}
|
||||
return minutes;
|
||||
}
|
||||
|
||||
uint32_t SQLiteDatabase::PruneTrafficMinutes(int64_t beforeTime) {
|
||||
return static_cast<uint32_t>(ExecuteUpdate("DELETE FROM server_traffic WHERE time < ?;", beforeTime));
|
||||
}
|
||||
@@ -222,6 +222,9 @@ class TestSQLDatabase : public GameDatabase {
|
||||
void InsertHealthSample(const HealthSample& sample) override {}
|
||||
std::vector<HealthSample> GetHealthSamples(int64_t from, int64_t to, int64_t bucketSeconds) override { return {}; }
|
||||
uint32_t PruneHealthSamples(int64_t beforeTime) override { return 0; }
|
||||
void InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) override {}
|
||||
std::vector<TrafficMinute> GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) override { return {}; }
|
||||
uint32_t PruneTrafficMinutes(int64_t beforeTime) override { return 0; }
|
||||
void InsertInstanceSamples(const std::vector<InstanceSample>& samples) override {}
|
||||
std::vector<InstanceSample> GetInstanceSamples(int64_t from, int64_t to, int64_t bucketSeconds, uint32_t zoneId) override { return {}; }
|
||||
void InsertPositionSamples(const std::vector<PositionSample>& samples) override {}
|
||||
|
||||
Reference in New Issue
Block a user