feat(dashboard): track the UGC server like auth and chat

Online state with the time it came up, down/up alerts, a UGC column in the
health history, /api/servers (every server with its process memory and CPU)
and /api/servers/ugc, and UGC gauges for Prometheus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 09:23:16 -05:00
parent 69803448bb
commit e669a4fa7e
18 changed files with 349 additions and 64 deletions

View File

@@ -78,7 +78,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IScheduledTasks::TaskSettings, name, schedule
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IScheduledTasks::TaskRun, id, task, trigger, actor, startedAt, finishedAt, status, summary, log);
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IServerConfig::Setting, file, name, fileValue, fileSource, webValue, webWins, secret, description, seenAt, updatedAt, updatedBy);
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IServerConfig::SettingChange, id, file, name, oldValue, oldWebWins, newValue, newWebWins, fileValue, secret, removed, revertOf, changedAt, accountId, changedBy);
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IServerHealth::HealthSample, time, players, worlds, authOnline, chatOnline, memoryKb);
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IServerHealth::HealthSample, time, players, worlds, authOnline, chatOnline, memoryKb, ugcEnabled, ugcOnline);
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IServerHealth::InstanceSample, time, zoneId, instanceId, cloneId, players);
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IPlayerPositions::PositionSample, time, characterId, zoneId, instanceId, cloneId, x, y, z);
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IPlayerPositions::PositionInstance, zoneId, instanceId, cloneId, first, last, players);
@@ -1230,8 +1230,8 @@ TEST_F(ParitySeeded, ChatLog) {
TEST_F(ParitySeeded, ServerHealth) {
Both("InsertHealthSample", [](GameDatabase& db) {
db.InsertHealthSample({ 1700000000, 5, 2, true, true, 100000 });
db.InsertHealthSample({ 1700000060, 7, 3, true, false, 110000 });
db.InsertHealthSample({ 1700000120, 3, 3, false, true, 90000 });
db.InsertHealthSample({ 1700000060, 7, 3, true, false, 110000, true, true });
db.InsertHealthSample({ 1700000120, 3, 3, false, true, 90000, true, false });
db.InsertHealthSample({ 1700000500, 1, 1, true, true, 50000 });
});
Both("GetHealthSamples", [](GameDatabase& db) { return json{ db.GetHealthSamples(1700000000, 1700001000, 60), db.GetHealthSamples(1700000000, 1700001000, 300), db.GetHealthSamples(1700000050, 1700000130, 60) }; });

View File

@@ -6,6 +6,9 @@
namespace ServerState {
ServerStatus g_AuthStatus{};
ServerStatus g_ChatStatus{};
ServerStatus g_UgcStatus{};
bool g_UgcEnabled{};
uint32_t g_UgcPid{};
std::vector<WorldInstanceInfo> g_WorldInstances{};
std::mutex g_StatusMutex{};
}
@@ -16,6 +19,8 @@ protected:
std::lock_guard lock(ServerState::g_StatusMutex);
ServerState::g_AuthStatus = {};
ServerState::g_ChatStatus = {};
ServerState::g_UgcStatus = {};
ServerState::g_UgcEnabled = false;
ServerState::g_WorldInstances.clear();
}
};
@@ -25,6 +30,8 @@ TEST_F(ServerStateTest, DefaultStateAllOffline) {
EXPECT_FALSE(json["auth"]["online"].get<bool>());
EXPECT_FALSE(json["chat"]["online"].get<bool>());
EXPECT_FALSE(json["ugc"]["online"].get<bool>());
EXPECT_FALSE(json["ugc"]["enabled"].get<bool>());
EXPECT_TRUE(json["worlds"].empty());
EXPECT_EQ(json["stats"]["onlinePlayers"].get<uint32_t>(), 0);
}
@@ -165,3 +172,25 @@ TEST_F(ServerStateTest, ServerStatusDefaults) {
EXPECT_EQ(status.players, 0);
EXPECT_EQ(status.version, "");
}
TEST_F(ServerStateTest, UgcServerComesAndGoes) {
{
std::lock_guard lock(ServerState::g_StatusMutex);
ServerState::g_UgcEnabled = true;
ServerState::g_UgcStatus.Set(true);
}
auto json = ServerState::GetServerStateJson();
EXPECT_TRUE(json["ugc"]["enabled"].get<bool>());
EXPECT_TRUE(json["ugc"]["online"].get<bool>());
const auto since = json["ugc"]["since"].get<int64_t>();
EXPECT_GT(since, 0);
// Still up: it keeps the time it came up
ServerState::g_UgcStatus.Set(true);
EXPECT_EQ(ServerState::GetServerStateJson()["ugc"]["since"].get<int64_t>(), since);
ServerState::g_UgcStatus.Set(false);
json = ServerState::GetServerStateJson();
EXPECT_FALSE(json["ugc"]["online"].get<bool>());
EXPECT_EQ(json["ugc"]["since"].get<int64_t>(), 0);
}