Add Prometheus stats for admin count (#26284)
* Add Prometheus stats for admin count Fixes #20828 Reports time series for admin count. Counts are separated by state (active, AFK, or deadminned) and admin rank. * Use static constructor instead of static readonly for the metric Docs recommend this due to inconsistent execution of C# static constructors. * Remove static usage, use IoC IMeterFactory. --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
committed by
GitHub
parent
cb97abb2de
commit
a1817a12db
@@ -0,0 +1,98 @@
|
|||||||
|
using System.Diagnostics.Metrics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Content.Server.Afk;
|
||||||
|
using Robust.Server.DataMetrics;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Managers;
|
||||||
|
|
||||||
|
// Handles metrics reporting for active admin count and such.
|
||||||
|
|
||||||
|
public sealed partial class AdminManager
|
||||||
|
{
|
||||||
|
private Dictionary<int, (int active, int afk, int deadminned)>? _adminOnlineCounts;
|
||||||
|
|
||||||
|
private const int SentinelRankId = -1;
|
||||||
|
|
||||||
|
[Dependency] private readonly IMetricsManager _metrics = default!;
|
||||||
|
[Dependency] private readonly IAfkManager _afkManager = default!;
|
||||||
|
[Dependency] private readonly IMeterFactory _meterFactory = default!;
|
||||||
|
|
||||||
|
private void InitializeMetrics()
|
||||||
|
{
|
||||||
|
_metrics.UpdateMetrics += MetricsOnUpdateMetrics;
|
||||||
|
|
||||||
|
var meter = _meterFactory.Create("SS14.AdminManager");
|
||||||
|
|
||||||
|
meter.CreateObservableGauge(
|
||||||
|
"admins_online_count",
|
||||||
|
MeasureAdminCount,
|
||||||
|
null,
|
||||||
|
"The count of online admins");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MetricsOnUpdateMetrics()
|
||||||
|
{
|
||||||
|
_sawmill.Verbose("Updating metrics");
|
||||||
|
|
||||||
|
var dict = new Dictionary<int, (int active, int afk, int deadminned)>();
|
||||||
|
|
||||||
|
foreach (var (session, reg) in _admins)
|
||||||
|
{
|
||||||
|
var rankId = reg.RankId ?? SentinelRankId;
|
||||||
|
|
||||||
|
ref var counts = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, rankId, out _);
|
||||||
|
|
||||||
|
if (reg.Data.Active)
|
||||||
|
{
|
||||||
|
if (_afkManager.IsAfk(session))
|
||||||
|
counts.afk += 1;
|
||||||
|
else
|
||||||
|
counts.active += 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
counts.deadminned += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither prometheus-net nor dotnet-counters seem to handle stuff well if we STOP returning measurements.
|
||||||
|
// i.e. if the last admin with a rank disconnects.
|
||||||
|
// So if we have EVER reported a rank, always keep reporting it.
|
||||||
|
if (_adminOnlineCounts != null)
|
||||||
|
{
|
||||||
|
foreach (var rank in _adminOnlineCounts.Keys)
|
||||||
|
{
|
||||||
|
CollectionsMarshal.GetValueRefOrAddDefault(dict, rank, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure "no rank" is always available. Avoid "no data".
|
||||||
|
CollectionsMarshal.GetValueRefOrAddDefault(dict, SentinelRankId, out _);
|
||||||
|
|
||||||
|
_adminOnlineCounts = dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Measurement<int>> MeasureAdminCount()
|
||||||
|
{
|
||||||
|
if (_adminOnlineCounts == null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
foreach (var (rank, (active, afk, deadminned)) in _adminOnlineCounts)
|
||||||
|
{
|
||||||
|
yield return new Measurement<int>(
|
||||||
|
active,
|
||||||
|
new KeyValuePair<string, object?>("state", "active"),
|
||||||
|
new KeyValuePair<string, object?>("rank", rank == SentinelRankId ? "none" : rank.ToString()));
|
||||||
|
|
||||||
|
yield return new Measurement<int>(
|
||||||
|
afk,
|
||||||
|
new KeyValuePair<string, object?>("state", "afk"),
|
||||||
|
new KeyValuePair<string, object?>("rank", rank == SentinelRankId ? "none" : rank.ToString()));
|
||||||
|
|
||||||
|
yield return new Measurement<int>(
|
||||||
|
deadminned,
|
||||||
|
new KeyValuePair<string, object?>("state", "deadminned"),
|
||||||
|
new KeyValuePair<string, object?>("rank", rank == SentinelRankId ? "none" : rank.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ using Robust.Shared.Utility;
|
|||||||
|
|
||||||
namespace Content.Server.Administration.Managers
|
namespace Content.Server.Administration.Managers
|
||||||
{
|
{
|
||||||
public sealed class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation
|
public sealed partial class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||||
@@ -34,6 +34,7 @@ namespace Content.Server.Administration.Managers
|
|||||||
[Dependency] private readonly IServerConsoleHost _consoleHost = default!;
|
[Dependency] private readonly IServerConsoleHost _consoleHost = default!;
|
||||||
[Dependency] private readonly IChatManager _chat = default!;
|
[Dependency] private readonly IChatManager _chat = default!;
|
||||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||||
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
|
|
||||||
private readonly Dictionary<ICommonSession, AdminReg> _admins = new();
|
private readonly Dictionary<ICommonSession, AdminReg> _admins = new();
|
||||||
private readonly HashSet<NetUserId> _promotedPlayers = new();
|
private readonly HashSet<NetUserId> _promotedPlayers = new();
|
||||||
@@ -49,6 +50,8 @@ namespace Content.Server.Administration.Managers
|
|||||||
private readonly AdminCommandPermissions _commandPermissions = new();
|
private readonly AdminCommandPermissions _commandPermissions = new();
|
||||||
private readonly AdminCommandPermissions _toolshedCommandPermissions = new();
|
private readonly AdminCommandPermissions _toolshedCommandPermissions = new();
|
||||||
|
|
||||||
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
public bool IsAdmin(ICommonSession session, bool includeDeAdmin = false)
|
public bool IsAdmin(ICommonSession session, bool includeDeAdmin = false)
|
||||||
{
|
{
|
||||||
return GetAdminData(session, includeDeAdmin) != null;
|
return GetAdminData(session, includeDeAdmin) != null;
|
||||||
@@ -181,6 +184,8 @@ namespace Content.Server.Administration.Managers
|
|||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
|
_sawmill = _logManager.GetSawmill("admin");
|
||||||
|
|
||||||
_netMgr.RegisterNetMessage<MsgUpdateAdminStatus>();
|
_netMgr.RegisterNetMessage<MsgUpdateAdminStatus>();
|
||||||
|
|
||||||
// Cache permissions for loaded console commands with the requisite attributes.
|
// Cache permissions for loaded console commands with the requisite attributes.
|
||||||
@@ -234,6 +239,8 @@ namespace Content.Server.Administration.Managers
|
|||||||
}
|
}
|
||||||
|
|
||||||
_toolshed.ActivePermissionController = this;
|
_toolshed.ActivePermissionController = this;
|
||||||
|
|
||||||
|
InitializeMetrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PromoteHost(ICommonSession player)
|
public void PromoteHost(ICommonSession player)
|
||||||
|
|||||||
Reference in New Issue
Block a user