]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add Prometheus stats for admin count (#26284)
authorPieter-Jan Briers <pieterjan.briers+git@gmail.com>
Sun, 24 Mar 2024 03:48:04 +0000 (04:48 +0100)
committerGitHub <noreply@github.com>
Sun, 24 Mar 2024 03:48:04 +0000 (14:48 +1100)
* 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>
Content.Server/Administration/Managers/AdminManager.Metrics.cs [new file with mode: 0644]
Content.Server/Administration/Managers/AdminManager.cs

diff --git a/Content.Server/Administration/Managers/AdminManager.Metrics.cs b/Content.Server/Administration/Managers/AdminManager.Metrics.cs
new file mode 100644 (file)
index 0000000..2fea931
--- /dev/null
@@ -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()));
+        }
+    }
+}
index 4eaa08fe9dde09c9ce8ec2740d1676c34e90f9b0..b1cca46e63f8e90a2f59bcf37108a34f0f4d50d3 100644 (file)
@@ -23,7 +23,7 @@ using Robust.Shared.Utility;
 
 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 IServerDbManager _dbManager = default!;
@@ -34,6 +34,7 @@ namespace Content.Server.Administration.Managers
         [Dependency] private readonly IServerConsoleHost _consoleHost = default!;
         [Dependency] private readonly IChatManager _chat = default!;
         [Dependency] private readonly ToolshedManager _toolshed = default!;
+        [Dependency] private readonly ILogManager _logManager = default!;
 
         private readonly Dictionary<ICommonSession, AdminReg> _admins = new();
         private readonly HashSet<NetUserId> _promotedPlayers = new();
@@ -49,6 +50,8 @@ namespace Content.Server.Administration.Managers
         private readonly AdminCommandPermissions _commandPermissions = new();
         private readonly AdminCommandPermissions _toolshedCommandPermissions = new();
 
+        private ISawmill _sawmill = default!;
+
         public bool IsAdmin(ICommonSession session, bool includeDeAdmin = false)
         {
             return GetAdminData(session, includeDeAdmin) != null;
@@ -181,6 +184,8 @@ namespace Content.Server.Administration.Managers
 
         public void Initialize()
         {
+            _sawmill = _logManager.GetSawmill("admin");
+
             _netMgr.RegisterNetMessage<MsgUpdateAdminStatus>();
 
             // Cache permissions for loaded console commands with the requisite attributes.
@@ -234,6 +239,8 @@ namespace Content.Server.Administration.Managers
             }
 
             _toolshed.ActivePermissionController = this;
+
+            InitializeMetrics();
         }
 
         public void PromoteHost(ICommonSession player)