From 213d9e9ea43de5eee2945be14f9e064a4b564513 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 31 Aug 2024 21:08:12 -0400 Subject: [PATCH 1/5] Add `launchTime` field to `DreamDaemonResponse` --- .../Models/Internal/DreamDaemonApiBase.cs | 10 +++++++++- .../Components/Session/SessionController.cs | 3 +++ .../Components/Watchdog/IWatchdog.cs | 5 +++++ .../Components/Watchdog/WatchdogBase.cs | 3 +++ .../Controllers/DreamDaemonController.cs | 1 + .../System/IProcessBase.cs | 8 +++++++- src/Tgstation.Server.Host/System/Process.cs | 17 +++++++++++++++++ .../Live/Instance/WatchdogTest.cs | 5 +++++ 8 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs index 6bde7948d0..ea5c16bc15 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Api.Models.Internal +using System; + +namespace Tgstation.Server.Api.Models.Internal { /// /// Base class for DreamDaemon API models. @@ -11,6 +13,12 @@ [ResponseOptions] public long? SessionId { get; set; } + /// + /// When the current server execution was started. + /// + [ResponseOptions] + public DateTimeOffset? LaunchTime { get; set; } + /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields. /// diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 754b672b7d..77a25fd979 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -124,6 +124,9 @@ namespace Tgstation.Server.Host.Components.Session /// public long? MemoryUsage => process.MemoryUsage; + /// + public DateTimeOffset? LaunchTime => process.LaunchTime; + /// /// The for the . /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 2220c56932..38ab770d33 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -19,6 +19,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// long? SessionId { get; } + /// + /// When the current server executions was started. + /// + DateTimeOffset? LaunchTime { get; } + /// /// The current . /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 5d9326ce7d..21871a680c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -38,6 +38,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public long? SessionId => GetActiveController()?.ReattachInformation.Id; + /// + public DateTimeOffset? LaunchTime => GetActiveController()?.LaunchTime; + /// public WatchdogStatus Status { diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 79e03ebb84..6b4ceb1a85 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -366,6 +366,7 @@ namespace Tgstation.Server.Host.Controllers firstIteration = false; result.Status = dd.Status; result.SessionId = dd.SessionId; + result.LaunchTime = dd.LaunchTime; } while (result.Status == WatchdogStatus.Online && !result.SessionId.HasValue); // this is the one invalid combo, it's not that racy diff --git a/src/Tgstation.Server.Host/System/IProcessBase.cs b/src/Tgstation.Server.Host/System/IProcessBase.cs index 6ae88dce50..b95b0bf928 100644 --- a/src/Tgstation.Server.Host/System/IProcessBase.cs +++ b/src/Tgstation.Server.Host/System/IProcessBase.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.System @@ -8,6 +9,11 @@ namespace Tgstation.Server.Host.System /// public interface IProcessBase { + /// + /// When the process was started. + /// + DateTimeOffset? LaunchTime { get; } + /// /// The resulting in the exit code of the process or if the process was detached. /// diff --git a/src/Tgstation.Server.Host/System/Process.cs b/src/Tgstation.Server.Host/System/Process.cs index e180cfc3cc..76f9ef56f3 100644 --- a/src/Tgstation.Server.Host/System/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -16,6 +16,23 @@ namespace Tgstation.Server.Host.System /// public int Id { get; } + /// + public DateTimeOffset? LaunchTime + { + get + { + try + { + return handle.StartTime; + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to get PID {pid}'s memory usage!", Id); + return null; + } + } + } + /// public Task Startup { get; } diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 08f436b26d..241c156dba 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -682,6 +682,7 @@ namespace Tgstation.Server.Tests.Live.Instance Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); Assert.IsNotNull(daemonStatus.ActiveCompileJob); Assert.IsFalse(daemonStatus.SessionId.HasValue); + Assert.IsFalse(daemonStatus.LaunchTime.HasValue); Assert.IsNull(daemonStatus.StagedCompileJob); Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.ActiveCompileJob.DMApiVersion); Assert.AreEqual(DreamDaemonSecurity.Trusted, daemonStatus.ActiveCompileJob.MinimumSecurityLevel); @@ -730,6 +731,7 @@ namespace Tgstation.Server.Tests.Live.Instance daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); Assert.IsFalse(daemonStatus.SessionId.HasValue); + Assert.IsFalse(daemonStatus.LaunchTime.HasValue); await ExpectGameDirectoryCount(1, cancellationToken); await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken, false); @@ -741,12 +743,15 @@ namespace Tgstation.Server.Tests.Live.Instance }, cancellationToken); Assert.AreEqual(string.Empty, daemonStatus.AdditionalParameters); Assert.IsFalse(daemonStatus.SessionId.HasValue); + Assert.IsFalse(daemonStatus.LaunchTime.HasValue); } long? sessionIdTracker; void ValidateSessionId(DreamDaemonResponse daemonStatus, bool? knownIncrease) { Assert.IsTrue(daemonStatus.SessionId.HasValue, $"Expected a session ID in the DreamDaemonResponse"); + Assert.IsTrue(daemonStatus.LaunchTime.HasValue); + Assert.IsTrue(daemonStatus.LaunchTime.Value >= DateTimeOffset.UtcNow.AddHours(-1)); if (sessionIdTracker.HasValue) if (knownIncrease.HasValue) From c5341b447137dd1bbcc158d96aacd1e92ec6ba52 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 31 Aug 2024 21:16:00 -0400 Subject: [PATCH 2/5] Move SystemD arg responsibility from service to Host Watchdog --- build/Version.props | 2 +- build/tgstation-server.service | 2 +- src/Tgstation.Server.Host.Console/Program.cs | 7 +++++++ .../Tgstation.Server.Host.Console.csproj | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/build/Version.props b/build/Version.props index af7c24e51a..2b9c5d5073 100644 --- a/build/Version.props +++ b/build/Version.props @@ -11,7 +11,7 @@ 16.0.0 7.2.1 5.9.0 - 1.4.1 + 1.5.0 1.2.1 2.0.0 netstandard2.0 diff --git a/build/tgstation-server.service b/build/tgstation-server.service index 98c03fee57..44ce74fe2e 100644 --- a/build/tgstation-server.service +++ b/build/tgstation-server.service @@ -11,7 +11,7 @@ User=tgstation-server Type=notify-reload NotifyAccess=all WorkingDirectory=/opt/tgstation-server -ExecStart=/usr/bin/dotnet Tgstation.Server.Host.Console.dll --appsettings-base-path=/etc/tgstation-server --General:SetupWizardMode=Never --Internal:UsingSystemD=true +ExecStart=/usr/bin/dotnet Tgstation.Server.Host.Console.dll --appsettings-base-path=/etc/tgstation-server --General:SetupWizardMode=Never TimeoutStartSec=600 Restart=always KillMode=process diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index c8383865af..26f5b8f5b5 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Hosting.Systemd; using Microsoft.Extensions.Logging; using Tgstation.Server.Common; @@ -44,6 +46,11 @@ namespace Tgstation.Server.Host.Console var trace = arguments.Remove("--trace-host-watchdog"); var debug = arguments.Remove("--debug-host-watchdog"); + const string SystemDArg = "--Internal:UsingSystemD=true"; + if (!arguments.Any(arg => arg.Equals(SystemDArg, StringComparison.OrdinalIgnoreCase)) + && SystemdHelpers.IsSystemdService()) + arguments.Add(SystemDArg); + using var loggerFactory = LoggerFactory.Create(builder => { if (trace) diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index 3cbf1c1a16..a405553d55 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -11,6 +11,8 @@ + + From 442ad8001745dbaedce2a760fbbaf0c8762dc102 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 31 Aug 2024 21:58:17 -0400 Subject: [PATCH 3/5] Add client count to DreamDaemon responses Closes #1909 --- build/Version.props | 4 ++-- src/DMAPI/tgs.dm | 2 +- src/DMAPI/tgs/v5/__interop_version.dm | 2 +- src/DMAPI/tgs/v5/_defines.dm | 1 + src/DMAPI/tgs/v5/topic.dm | 4 +++- src/DMAPI/tgs/v5/undefs.dm | 3 ++- .../Models/Internal/DreamDaemonApiBase.cs | 6 ++++++ .../Components/Interop/Topic/TopicResponse.cs | 5 +++++ .../Components/Watchdog/IWatchdog.cs | 5 +++++ .../Components/Watchdog/WatchdogBase.cs | 8 ++++++++ .../Controllers/DreamDaemonController.cs | 1 + .../Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs | 5 +++++ 12 files changed, 40 insertions(+), 6 deletions(-) diff --git a/build/Version.props b/build/Version.props index 2b9c5d5073..a6365b35ad 100644 --- a/build/Version.props +++ b/build/Version.props @@ -9,8 +9,8 @@ 7.0.0 13.7.0 16.0.0 - 7.2.1 - 5.9.0 + 7.3.0 + 5.10.0 1.5.0 1.2.1 2.0.0 diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 4766b3dfe6..42f2d5fc31 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,7 +1,7 @@ // tgstation-server DMAPI // The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in IETF RFC 2119. -#define TGS_DMAPI_VERSION "7.2.1" +#define TGS_DMAPI_VERSION "7.3.0" // All functions and datums outside this document are subject to change with any version and should not be relied on. diff --git a/src/DMAPI/tgs/v5/__interop_version.dm b/src/DMAPI/tgs/v5/__interop_version.dm index f4806f7adb..29ea239ad8 100644 --- a/src/DMAPI/tgs/v5/__interop_version.dm +++ b/src/DMAPI/tgs/v5/__interop_version.dm @@ -1 +1 @@ -"5.9.0" +"5.10.0" diff --git a/src/DMAPI/tgs/v5/_defines.dm b/src/DMAPI/tgs/v5/_defines.dm index 92c7a8388a..a47bfd7800 100644 --- a/src/DMAPI/tgs/v5/_defines.dm +++ b/src/DMAPI/tgs/v5/_defines.dm @@ -95,6 +95,7 @@ #define DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION "newServerVersion" #define DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE "broadcastMessage" +#define DMAPI5_TOPIC_RESPONSE_CLIENT_COUNT "clientCount" #define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE "commandResponse" #define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage" #define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses" diff --git a/src/DMAPI/tgs/v5/topic.dm b/src/DMAPI/tgs/v5/topic.dm index e1f2cb6385..59e5e63e5c 100644 --- a/src/DMAPI/tgs/v5/topic.dm +++ b/src/DMAPI/tgs/v5/topic.dm @@ -149,7 +149,9 @@ if(DMAPI5_TOPIC_COMMAND_HEALTHCHECK) if(event_handler && event_handler.receive_health_checks) event_handler.HandleEvent(TGS_EVENT_HEALTH_CHECK) - return TopicResponse() + var/list/health_check_response = TopicResponse() + health_check_response[DMAPI5_TOPIC_RESPONSE_CLIENT_COUNT] = TGS_CLIENT_COUNT + return health_check_response; if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH) detached = FALSE diff --git a/src/DMAPI/tgs/v5/undefs.dm b/src/DMAPI/tgs/v5/undefs.dm index 237207fdfd..d8dbf7a781 100644 --- a/src/DMAPI/tgs/v5/undefs.dm +++ b/src/DMAPI/tgs/v5/undefs.dm @@ -17,8 +17,8 @@ #undef DMAPI5_BRIDGE_COMMAND_EVENT #undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER +#undef DMAPI5_PARAMETER_CLIENT_COUNT #undef DMAPI5_PARAMETER_CUSTOM_COMMANDS -#undef DMAPI5_PARAMETER_TOPIC_PORT #undef DMAPI5_CHUNK #undef DMAPI5_CHUNK_PAYLOAD @@ -95,6 +95,7 @@ #undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION #undef DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE +#undef DMAPI5_TOPIC_RESPONSE_CLIENT_COUNT #undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE #undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE #undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs index ea5c16bc15..e3411b8115 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonApiBase.cs @@ -19,6 +19,12 @@ namespace Tgstation.Server.Api.Models.Internal [ResponseOptions] public DateTimeOffset? LaunchTime { get; set; } + /// + /// The last known count of connected players. Requires to not be 0 and a game server interop version >= 5.10.0 to populate. + /// + [ResponseOptions] + public int? ClientCount { get; set; } + /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields. /// diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs index 12db6f3ef1..e5b30150de 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs @@ -36,5 +36,10 @@ namespace Tgstation.Server.Host.Components.Interop.Topic /// public IReadOnlyCollection? MissingChunks { get; set; } + + /// + /// The number of connected clients to the game. Added in Interop 5.10.0. + /// + public int? ClientCount { get; } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 38ab770d33..35da1504f8 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -24,6 +24,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// DateTimeOffset? LaunchTime { get; } + /// + /// Last known client count queried from the DMAPI. Requires health checks to be enabled to populate. + /// + int? ClientCount { get; } + /// /// The current . /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 21871a680c..3c67ca495c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -38,6 +38,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public long? SessionId => GetActiveController()?.ReattachInformation.Id; + /// + public int? ClientCount { get; private set; } + /// public DateTimeOffset? LaunchTime => GetActiveController()?.LaunchTime; @@ -570,6 +573,7 @@ namespace Tgstation.Server.Host.Components.Watchdog // since neither server is running, this is safe to do LastLaunchParameters = ActiveLaunchParameters; healthChecksMissed = 0; + ClientCount = null; try { @@ -667,6 +671,7 @@ namespace Tgstation.Server.Host.Components.Watchdog // we lost the server, just restart entirely // DCT: Operation must always run await DisposeAndNullControllers(CancellationToken.None); + ClientCount = null; const string FailReattachMessage = "Unable to properly reattach to server! Restarting watchdog..."; Logger.LogWarning(FailReattachMessage); @@ -1183,7 +1188,10 @@ namespace Tgstation.Server.Host.Components.Watchdog } } else + { healthChecksMissed = 0; + ClientCount = response.ClientCount; + } return MonitorAction.Continue; } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 6b4ceb1a85..e6760f0f17 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -367,6 +367,7 @@ namespace Tgstation.Server.Host.Controllers result.Status = dd.Status; result.SessionId = dd.SessionId; result.LaunchTime = dd.LaunchTime; + result.ClientCount = dd.ClientCount; } while (result.Status == WatchdogStatus.Online && !result.SessionId.HasValue); // this is the one invalid combo, it's not that racy diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 241c156dba..d7930e0cc6 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -753,6 +753,9 @@ namespace Tgstation.Server.Tests.Live.Instance Assert.IsTrue(daemonStatus.LaunchTime.HasValue); Assert.IsTrue(daemonStatus.LaunchTime.Value >= DateTimeOffset.UtcNow.AddHours(-1)); + if (daemonStatus.ClientCount.HasValue) + Assert.AreEqual(0, daemonStatus.ClientCount.Value); + if (sessionIdTracker.HasValue) if (knownIncrease.HasValue) if (knownIncrease.Value) @@ -861,6 +864,8 @@ namespace Tgstation.Server.Tests.Live.Instance // Ensure it's responding to health checks await Task.WhenAny(Task.Delay(7000, cancellationToken), ourProcessHandler.Lifetime); Assert.IsFalse(ddProc.HasExited); + var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(0, daemonStatus.ClientCount); // check DD agrees var topicRequestResult = await SendTestTopic( From 33dd07152dee981bb7d4359b11d7c0e8f11079f5 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 31 Aug 2024 21:58:27 -0400 Subject: [PATCH 4/5] API Bump to 10.8 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index a6365b35ad..92901d070f 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,7 +5,7 @@ 6.9.2 5.2.0 - 10.7.0 + 10.8.0 7.0.0 13.7.0 16.0.0 From debeba2aa22c8eced4966fed3a3e2ab38a1c6b51 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 31 Aug 2024 21:58:43 -0400 Subject: [PATCH 5/5] Version bump to 6.10 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 92901d070f..c6573e2f14 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.9.2 + 6.10.0 5.2.0 10.8.0 7.0.0