From bd1deb0a9b9fdd45cb10d39311fa2783be60440c Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 10 Aug 2024 17:18:45 -0400 Subject: [PATCH 1/8] Add 30 minute cache to GitHub releases requests Closes #1831 --- build/Version.props | 6 +- .../Models/Response/AdministrationResponse.cs | 5 ++ .../AdministrationClient.cs | 2 +- .../IAdministrationClient.cs | 3 +- .../Controllers/AdministrationController.cs | 78 +++++++++++++------ .../Live/AdministrationTest.cs | 22 +++++- .../Live/Instance/JobsHubTests.cs | 2 +- .../Live/RawRequestTests.cs | 2 +- .../Live/TestLiveServer.cs | 2 +- 9 files changed, 87 insertions(+), 35 deletions(-) diff --git a/build/Version.props b/build/Version.props index 74c77f27a7..6e45564094 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,10 +5,10 @@ 6.8.0 5.1.0 - 10.6.0 + 10.7.0 7.0.0 - 13.6.0 - 15.6.0 + 13.7.0 + 16.0.0 7.2.0 5.9.0 1.4.1 diff --git a/src/Tgstation.Server.Api/Models/Response/AdministrationResponse.cs b/src/Tgstation.Server.Api/Models/Response/AdministrationResponse.cs index 55d7f646e5..2db5d354a7 100644 --- a/src/Tgstation.Server.Api/Models/Response/AdministrationResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/AdministrationResponse.cs @@ -16,5 +16,10 @@ namespace Tgstation.Server.Api.Models.Response /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is not equal to 4 the update cannot be applied due to API changes. /// public Version? LatestVersion { get; set; } + + /// + /// This response is cached. This field indicates the when it was generated. + /// + public DateTimeOffset? GeneratedAt { get; set; } } } diff --git a/src/Tgstation.Server.Client/AdministrationClient.cs b/src/Tgstation.Server.Client/AdministrationClient.cs index 2d6841795c..68c7fecd34 100644 --- a/src/Tgstation.Server.Client/AdministrationClient.cs +++ b/src/Tgstation.Server.Client/AdministrationClient.cs @@ -24,7 +24,7 @@ namespace Tgstation.Server.Client } /// - public ValueTask Read(CancellationToken cancellationToken) => ApiClient.Read(Routes.Administration, cancellationToken); + public ValueTask Read(bool forceFresh, CancellationToken cancellationToken) => ApiClient.Read($"{Routes.Administration}?fresh={forceFresh}", cancellationToken); /// public async ValueTask Update( diff --git a/src/Tgstation.Server.Client/IAdministrationClient.cs b/src/Tgstation.Server.Client/IAdministrationClient.cs index 38d0898345..6d5d88f951 100644 --- a/src/Tgstation.Server.Client/IAdministrationClient.cs +++ b/src/Tgstation.Server.Client/IAdministrationClient.cs @@ -17,9 +17,10 @@ namespace Tgstation.Server.Client /// /// Get the represented by the . /// + /// If the response will be forcefully regenerated. /// The for the operation. /// A resulting in the represented by the . - ValueTask Read(CancellationToken cancellationToken); + ValueTask Read(bool forceFresh = false, CancellationToken cancellationToken = default); /// /// Updates the setttings. diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index 2ccc4f4c8b..d4c1335df7 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using System.Web; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -42,6 +43,11 @@ namespace Tgstation.Server.Host.Controllers /// const string OctokitException = "Bad GitHub API response, check configuration!"; + /// + /// The key for . + /// + static readonly object ReadCacheKey = new(); + /// /// The for the . /// @@ -77,6 +83,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly IFileTransferTicketProvider fileTransferService; + /// + /// The for the . + /// + readonly IMemoryCache cacheService; + /// /// The for the . /// @@ -94,6 +105,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of . /// The value of . /// The value of . + /// The value of . /// The for the . /// The containing value of . /// The for the . @@ -107,6 +119,7 @@ namespace Tgstation.Server.Host.Controllers IIOManager ioManager, IPlatformIdentifier platformIdentifier, IFileTransferTicketProvider fileTransferService, + IMemoryCache cacheService, ILogger logger, IOptions fileLoggingConfigurationOptions, IApiHeadersProvider apiHeadersProvider) @@ -124,12 +137,14 @@ namespace Tgstation.Server.Host.Controllers this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService)); + this.cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService)); fileLoggingConfiguration = fileLoggingConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(fileLoggingConfigurationOptions)); } /// /// Get server information. /// + /// If , the cache should be bypassed. /// The for the operation. /// A resulting in the for the operation. /// Retrieved data successfully. @@ -140,39 +155,56 @@ namespace Tgstation.Server.Host.Controllers [ProducesResponseType(typeof(AdministrationResponse), 200)] [ProducesResponseType(typeof(ErrorMessageResponse), 424)] [ProducesResponseType(typeof(ErrorMessageResponse), 429)] - public async ValueTask Read(CancellationToken cancellationToken) + public async ValueTask Read([FromQuery] bool? fresh, CancellationToken cancellationToken) { try { - Version? greatestVersion = null; - Uri? repoUrl = null; - try + async Task CacheFactory() { - var gitHubService = gitHubServiceFactory.CreateService(); - var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken); - var releases = await gitHubService.GetTgsReleases(cancellationToken); - - foreach (var kvp in releases) + Version? greatestVersion = null; + Uri? repoUrl = null; + try { - var version = kvp.Key; - var release = kvp.Value; - if (version.Major > 3 // Forward/backward compatible but not before TGS4 - && (greatestVersion == null || version > greatestVersion)) - greatestVersion = version; + var gitHubService = gitHubServiceFactory.CreateService(); + var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken); + var releases = await gitHubService.GetTgsReleases(cancellationToken); + + foreach (var kvp in releases) + { + var version = kvp.Key; + var release = kvp.Value; + if (version.Major > 3 // Forward/backward compatible but not before TGS4 + && (greatestVersion == null || version > greatestVersion)) + greatestVersion = version; + } + + repoUrl = await repositoryUrlTask; + } + catch (NotFoundException e) + { + Logger.LogWarning(e, "Not found exception while retrieving upstream repository info!"); } - repoUrl = await repositoryUrlTask; - } - catch (NotFoundException e) - { - Logger.LogWarning(e, "Not found exception while retrieving upstream repository info!"); + return Json(new AdministrationResponse + { + LatestVersion = greatestVersion, + TrackedRepositoryUrl = repoUrl, + GeneratedAt = DateTimeOffset.UtcNow, + }); } - return Json(new AdministrationResponse + var ttl = TimeSpan.FromMinutes(30); + Task task; + if (fresh == true || !cacheService.TryGetValue(ReadCacheKey, out var rawCacheObject)) { - LatestVersion = greatestVersion, - TrackedRepositoryUrl = repoUrl, - }); + using var entry = cacheService.CreateEntry(ReadCacheKey); + entry.AbsoluteExpirationRelativeToNow = ttl; + entry.Value = task = CacheFactory(); + } + else + task = (Task)rawCacheObject!; + + return await task; } catch (RateLimitExceededException e) { diff --git a/tests/Tgstation.Server.Tests/Live/AdministrationTest.cs b/tests/Tgstation.Server.Tests/Live/AdministrationTest.cs index adfb009d69..afeabef6f5 100644 --- a/tests/Tgstation.Server.Tests/Live/AdministrationTest.cs +++ b/tests/Tgstation.Server.Tests/Live/AdministrationTest.cs @@ -1,10 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; +using System; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Client; @@ -54,10 +54,24 @@ namespace Tgstation.Server.Tests.Live async Task TestRead(CancellationToken cancellationToken) { - var model = await client.Read(cancellationToken); + var model = await client.Read(false, cancellationToken); //we've released a few 5.x versions now, check the release checker is at least somewhat functional Assert.IsTrue(4 < model.LatestVersion.Major); + Assert.IsNotNull(model.TrackedRepositoryUrl); + Assert.IsTrue(model.GeneratedAt.HasValue); + Assert.IsTrue(model.GeneratedAt.Value <= DateTimeOffset.UtcNow); + + // test the cache + var newerModel = await client.Read(false, cancellationToken); + Assert.AreEqual(model.GeneratedAt, newerModel.GeneratedAt); + + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + + var newestModel = await client.Read(true, cancellationToken); + Assert.AreNotEqual(model.GeneratedAt, newestModel.GeneratedAt); + Assert.IsNotNull(newestModel.GeneratedAt); + Assert.IsTrue(model.GeneratedAt < newestModel.GeneratedAt); } } } diff --git a/tests/Tgstation.Server.Tests/Live/Instance/JobsHubTests.cs b/tests/Tgstation.Server.Tests/Live/Instance/JobsHubTests.cs index b3149556e5..70b7e9c030 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/JobsHubTests.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/JobsHubTests.cs @@ -262,7 +262,7 @@ namespace Tgstation.Server.Tests.Live.Instance Assert.AreEqual(HubConnectionState.Disconnected, permlessConn.State); // force token refreshs - await Task.WhenAll(permedUser.Administration.Read(cancellationToken).AsTask(), permlessUser.Instances.List(null, cancellationToken).AsTask()); + await Task.WhenAll(permedUser.Administration.Read(false, cancellationToken).AsTask(), permlessUser.Instances.List(null, cancellationToken).AsTask()); if (!permlessPsId.HasValue) { diff --git a/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs b/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs index 3e20609e60..30e6b9e78e 100644 --- a/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs +++ b/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs @@ -216,7 +216,7 @@ namespace Tgstation.Server.Tests.Live }; var badClient = clientFactory.CreateFromToken(serverClient.Url, newToken); - await ApiAssert.ThrowsException(() => badClient.Administration.Read(cancellationToken)); + await ApiAssert.ThrowsException(() => badClient.Administration.Read(false, cancellationToken)); await ApiAssert.ThrowsException(() => badClient.ServerInformation(cancellationToken)); } diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index bfc4538c68..b34b14f066 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -670,7 +670,7 @@ namespace Tgstation.Server.Tests.Live "asdfasdfasdfasdf"); await using var node1BadClient = clientFactory.CreateFromToken(node1.RootUrl, controllerUserClient.Token); - await ApiAssert.ThrowsException(() => node1BadClient.Administration.Read(cancellationToken)); + await ApiAssert.ThrowsException(() => node1BadClient.Administration.Read(false, cancellationToken)); // check instance info is not shared var controllerInstance = await controllerClient.Instances.CreateOrAttach( From 792947da4dca3b09b38af323fe2a754b383546c6 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 10 Aug 2024 17:23:07 -0400 Subject: [PATCH 2/8] Add configuration option to force use of dreamdaemon.exe --- build/Version.props | 2 +- .../Components/Engine/WindowsByondInstaller.cs | 2 +- .../Configuration/SessionConfiguration.cs | 5 +++++ src/Tgstation.Server.Host/appsettings.yml | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/build/Version.props b/build/Version.props index 6e45564094..c947fc3c5c 100644 --- a/build/Version.props +++ b/build/Version.props @@ -4,7 +4,7 @@ 6.8.0 - 5.1.0 + 5.2.0 10.7.0 7.0.0 13.7.0 diff --git a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs index 35bcbf63dc..ce2c7704e2 100644 --- a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs @@ -224,7 +224,7 @@ namespace Tgstation.Server.Host.Components.Engine /// protected override string GetDreamDaemonName(Version byondVersion, out bool supportsCli) { - supportsCli = byondVersion >= DDExeVersion; + supportsCli = byondVersion >= DDExeVersion && !sessionConfiguration.ForceUseDreamDaemonExe; return supportsCli ? "dd.exe" : "dreamdaemon.exe"; } diff --git a/src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs b/src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs index c543698b90..d7ee3e1c9d 100644 --- a/src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs @@ -29,5 +29,10 @@ /// If , deployments that fail will not be immediately cleaned up. They will be cleaned up the next time the instance is onlined. /// public bool DelayCleaningFailedDeployments { get; set; } + + /// + /// If set dd.exe will not be used on Windows systems in versions where it is present. Instead dreamdaemon.exe will always be used. + /// + public bool ForceUseDreamDaemonExe { get; set; } } } diff --git a/src/Tgstation.Server.Host/appsettings.yml b/src/Tgstation.Server.Host/appsettings.yml index 4868f519fd..526f38ab38 100644 --- a/src/Tgstation.Server.Host/appsettings.yml +++ b/src/Tgstation.Server.Host/appsettings.yml @@ -23,6 +23,7 @@ Session: HighPriorityLiveDreamDaemon: false # If DreamDaemon instances should run as higher priority processes LowPriorityDeploymentProcesses: true # If TGS Deployments should run as lower priority processes DelayCleaningFailedDeployments: false # If true, deployments that fail will not be immediately cleaned up. They will be cleaned up the next time the instance is onlined + ForceUseDreamDaemonExe: false # If true, dd.exe will not be used on Windows systems in versions where it is present. Instead dreamdaemon.exe will always be used. FileLogging: Directory: # Directory in which log files are stored. Windows default: %PROGRAMDATA%/tgstation-server. Linux default: /var/log/tgstation-server Disable: true # Disable file logging entirely From 788fbb592101c9e12ed71247a6afd167d68b0695 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 10 Aug 2024 19:05:32 -0400 Subject: [PATCH 3/8] Minor code cleanups --- .../Chat/Commands/RevisionCommand.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs index 39ed128d59..1bd2a8b91e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs @@ -50,7 +50,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands public async ValueTask Invoke(string arguments, ChatUser user, CancellationToken cancellationToken) { string result; - if (arguments.Split(' ').Any(x => x.ToUpperInvariant() == "--REPO")) + if (arguments.Split(' ').Any(x => x.Equals("--repo", StringComparison.OrdinalIgnoreCase))) { if (repositoryManager.CloneInProgress || repositoryManager.InUse) return new MessageContent @@ -58,15 +58,13 @@ namespace Tgstation.Server.Host.Components.Chat.Commands Text = "Repository busy! Try again later", }; - using (var repo = await repositoryManager.LoadRepository(cancellationToken)) - { - if (repo == null) - return new MessageContent - { - Text = "Repository unavailable!", - }; - result = repo.Head; - } + using var repo = await repositoryManager.LoadRepository(cancellationToken); + if (repo == null) + return new MessageContent + { + Text = "Repository unavailable!", + }; + result = repo.Head; } else { From 89d70be78207632267e7dd5d1135f84c26dd1784 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 10 Aug 2024 19:06:10 -0400 Subject: [PATCH 4/8] Implement repository recloning Closes #1016 --- src/Tgstation.Server.Api/Models/JobCode.cs | 6 ++ .../Rights/RepositoryRights.cs | 5 + .../Components/IRepositoryClient.cs | 7 ++ .../Components/RepositoryClient.cs | 3 + .../Components/Engine/OpenDreamInstaller.cs | 1 + .../Components/Events/EventType.cs | 2 +- .../Components/Repository/IRepository.cs | 2 + .../Components/Repository/Repository.cs | 67 ++++++++---- .../Repository/RepositoryUpdateService.cs | 102 +++++++++++++++--- .../Controllers/RepositoryController.cs | 57 ++++++++-- .../Live/Instance/RepositoryTest.cs | 19 ++++ .../Tgstation.Server.Tests/TestRepository.cs | 2 +- 12 files changed, 228 insertions(+), 45 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/JobCode.cs b/src/Tgstation.Server.Api/Models/JobCode.cs index be5db8d5a6..1c20107b59 100644 --- a/src/Tgstation.Server.Api/Models/JobCode.cs +++ b/src/Tgstation.Server.Api/Models/JobCode.cs @@ -108,5 +108,11 @@ namespace Tgstation.Server.Api.Models /// [Description("Reconnect chat bot")] ReconnectChatBot, + + /// + /// When a repository is recloned. + /// + [Description("Reclone repository")] + RepositoryReclone, } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index f9d134820b..407437ce5d 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -82,5 +82,10 @@ namespace Tgstation.Server.Api.Rights /// User may change submodule update settings. /// ChangeSubmoduleUpdate = 1 << 13, + + /// + /// User may trigger repository recloning. + /// + Reclone = 1 << 14, } } diff --git a/src/Tgstation.Server.Client/Components/IRepositoryClient.cs b/src/Tgstation.Server.Client/Components/IRepositoryClient.cs index 9d3bef806c..51d8bdfd80 100644 --- a/src/Tgstation.Server.Client/Components/IRepositoryClient.cs +++ b/src/Tgstation.Server.Client/Components/IRepositoryClient.cs @@ -40,5 +40,12 @@ namespace Tgstation.Server.Client.Components /// The for the operation. /// A resulting in the . ValueTask Delete(CancellationToken cancellationToken); + + /// + /// Deletes and reclones the repository. + /// + /// The for the operation. + /// A resulting in the . + ValueTask Reclone(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/RepositoryClient.cs b/src/Tgstation.Server.Client/Components/RepositoryClient.cs index 124f1ec5b2..5711612024 100644 --- a/src/Tgstation.Server.Client/Components/RepositoryClient.cs +++ b/src/Tgstation.Server.Client/Components/RepositoryClient.cs @@ -44,5 +44,8 @@ namespace Tgstation.Server.Client.Components /// public ValueTask Update(RepositoryUpdateRequest repository, CancellationToken cancellationToken) => apiClient.Update(Routes.Repository, repository ?? throw new ArgumentNullException(nameof(repository)), instance.Id!.Value, cancellationToken); + + /// + public ValueTask Reclone(CancellationToken cancellationToken) => apiClient.Patch(Routes.Repository, instance.Id!.Value, cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs index e552d16b40..f99d70bfa4 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs @@ -176,6 +176,7 @@ namespace Tgstation.Server.Host.Components.Engine null, null, true, + false, progressSection2, cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Events/EventType.cs b/src/Tgstation.Server.Host/Components/Events/EventType.cs index 5d8c0b9054..711d6ca246 100644 --- a/src/Tgstation.Server.Host/Components/Events/EventType.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventType.cs @@ -12,7 +12,7 @@ RepoResetOrigin, /// - /// Parameters: Checkout target. + /// Parameters: Checkout target, hard reset flag (If "True", this is actually a hard reset, not a checkout). /// [EventScript("RepoCheckout")] RepoCheckout, diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 65bcd1d61e..e736c4873e 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -47,6 +47,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The optional username used for fetching from submodule repositories. /// The optional password used for fetching from submodule repositories. /// If a submodule update should be attempted after the merge. + /// If a hard reset to the target committish should be performed instead of a checkout. /// The optional to report progress of the operation. /// The for the operation. /// A representing the running operation. @@ -55,6 +56,7 @@ namespace Tgstation.Server.Host.Components.Repository string? username, string? password, bool updateSubmodules, + bool moveCurrentReference, JobProgressReporter? progressReporter, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 972fae31cc..6873d42957 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -295,7 +295,7 @@ namespace Tgstation.Server.Host.Components.Repository var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha; logger.LogDebug("Merge conflict, aborting and reverting to {revertTarget}", revertTo); progressReporter.ReportProgress(0); - RawCheckout(revertTo, progressReporter.CreateSection("Hard Reset to {revertTo}", 1.0), cancellationToken); + RawCheckout(revertTo, false, progressReporter.CreateSection("Hard Reset to {revertTo}", 1.0), cancellationToken); cancellationToken.ThrowIfCancellationRequested(); } @@ -376,19 +376,21 @@ namespace Tgstation.Server.Host.Components.Repository string? username, string? password, bool updateSubmodules, + bool moveCurrentReference, JobProgressReporter? progressReporter, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(committish); logger.LogDebug("Checkout object: {committish}...", committish); - await eventConsumer.HandleEvent(EventType.RepoCheckout, new List { committish }, false, cancellationToken); + await eventConsumer.HandleEvent(EventType.RepoCheckout, new List { committish, moveCurrentReference.ToString() }, false, cancellationToken); await Task.Factory.StartNew( () => { libGitRepo.RemoveUntrackedFiles(); RawCheckout( committish, + moveCurrentReference, progressReporter?.CreateSection(null, updateSubmodules ? 2.0 / 3 : 1.0), cancellationToken); }, @@ -879,9 +881,10 @@ namespace Tgstation.Server.Host.Components.Repository /// Runs a blocking force checkout to . /// /// The committish to checkout. + /// If a hard reset should actually be performed. /// The optional for the operation. /// The for the operation. - void RawCheckout(string committish, JobProgressReporter? progressReporter, CancellationToken cancellationToken) + void RawCheckout(string committish, bool moveCurrentReference, JobProgressReporter? progressReporter, CancellationToken cancellationToken) { logger.LogTrace("Checkout: {committish}", committish); @@ -900,34 +903,52 @@ namespace Tgstation.Server.Host.Components.Repository cancellationToken.ThrowIfCancellationRequested(); - void RunCheckout() => commands.Checkout( - libGitRepo, - checkoutOptions, - committish); - - try + if (moveCurrentReference) { - RunCheckout(); + if (Reference == NoReference) + throw new InvalidOperationException("Cannot move current reference when not on reference!"); + + var gitObject = libGitRepo.Lookup(committish); + if (gitObject == null) + throw new JobException($"Could not find committish: {committish}"); + + var commit = gitObject.Peel(); + + cancellationToken.ThrowIfCancellationRequested(); + + libGitRepo.Reset(ResetMode.Hard, commit, checkoutOptions); } - catch (NotFoundException) + else { - // Maybe (likely) a remote? - var remoteName = $"origin/{committish}"; - var remoteBranch = libGitRepo.Branches.FirstOrDefault( - branch => branch.FriendlyName.Equals(remoteName, StringComparison.Ordinal)); - cancellationToken.ThrowIfCancellationRequested(); + void RunCheckout() => commands.Checkout( + libGitRepo, + checkoutOptions, + committish); - if (remoteBranch == default) - throw; + try + { + RunCheckout(); + } + catch (NotFoundException) + { + // Maybe (likely) a remote? + var remoteName = $"origin/{committish}"; + var remoteBranch = libGitRepo.Branches.FirstOrDefault( + branch => branch.FriendlyName.Equals(remoteName, StringComparison.Ordinal)); + cancellationToken.ThrowIfCancellationRequested(); - logger.LogDebug("Creating local branch for {remoteBranchFriendlyName}...", remoteBranch.FriendlyName); - var branch = libGitRepo.CreateBranch(committish, remoteBranch.Tip); + if (remoteBranch == default) + throw; - libGitRepo.Branches.Update(branch, branchUpdate => branchUpdate.TrackedBranch = remoteBranch.CanonicalName); + logger.LogDebug("Creating local branch for {remoteBranchFriendlyName}...", remoteBranch.FriendlyName); + var branch = libGitRepo.CreateBranch(committish, remoteBranch.Tip); - cancellationToken.ThrowIfCancellationRequested(); + libGitRepo.Branches.Update(branch, branchUpdate => branchUpdate.TrackedBranch = remoteBranch.CanonicalName); - RunCheckout(); + cancellationToken.ThrowIfCancellationRequested(); + + RunCheckout(); + } } cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs index a7aca78c4b..9861a85418 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs @@ -22,11 +22,6 @@ namespace Tgstation.Server.Host.Components.Repository /// sealed class RepositoryUpdateService { - /// - /// The for the . - /// - readonly RepositoryUpdateRequest model; - /// /// The current for the . /// @@ -50,19 +45,16 @@ namespace Tgstation.Server.Host.Components.Repository /// /// Initializes a new instance of the class. /// - /// The value of . /// The value of . /// The value of . /// The value of . /// The value of . public RepositoryUpdateService( - RepositoryUpdateRequest model, RepositorySettings currentModel, User initiatingUser, ILogger logger, long instanceId) { - this.model = model ?? throw new ArgumentNullException(nameof(model)); this.currentModel = currentModel ?? throw new ArgumentNullException(nameof(currentModel)); this.initiatingUser = initiatingUser ?? throw new ArgumentNullException(nameof(initiatingUser)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -139,24 +131,25 @@ namespace Tgstation.Server.Host.Components.Repository /// /// The job entrypoint used by to update the repository's current HEAD. /// - /// The the job is running on. only when performing an instance move operation. + /// The . + /// The the job is running on. /// The for the operation. - /// The running , ignored. /// The for the job. /// The for the operation. /// A representing the running operation. #pragma warning disable CA1502, CA1506 // TODO: Decomplexify public async ValueTask RepositoryUpdateJob( + RepositoryUpdateRequest model, IInstanceCore? instance, IDatabaseContextFactory databaseContextFactory, - Job job, JobProgressReporter progressReporter, CancellationToken cancellationToken) #pragma warning restore CA1502, CA1506 { + ArgumentNullException.ThrowIfNull(model); ArgumentNullException.ThrowIfNull(instance); - - _ = job; // shuts up an IDE warning + ArgumentNullException.ThrowIfNull(databaseContextFactory); + ArgumentNullException.ThrowIfNull(progressReporter); var repoManager = instance.RepositoryManager; using var repo = await repoManager.LoadRepository(cancellationToken) ?? throw new JobException(ErrorCode.RepoMissing); @@ -315,6 +308,7 @@ namespace Tgstation.Server.Host.Components.Repository currentModel.AccessUser, currentModel.AccessToken, updateSubmodules, + false, NextProgressReporter("Checkout"), cancellationToken); await CallLoadRevInfo(); // we've either seen origin before or what we're checking out is on origin @@ -579,6 +573,7 @@ namespace Tgstation.Server.Host.Components.Repository currentModel.AccessUser, currentModel.AccessToken, true, + false, progressReporter.CreateSection($"Checkout {startReference ?? startSha[..7]}", secondStep ? 0.5 : 1.0), default); @@ -588,5 +583,86 @@ namespace Tgstation.Server.Host.Components.Repository throw; } } + + /// + /// The job entrypoint used by to reclone a repository. + /// + /// The the job is running on. + /// The for the operation. + /// The for the job. + /// The for the operation. + /// A representing the running operation. + public async ValueTask RepositoryRecloneJob( + IInstanceCore? instance, + IDatabaseContextFactory databaseContextFactory, + JobProgressReporter progressReporter, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(instance); + ArgumentNullException.ThrowIfNull(databaseContextFactory); + ArgumentNullException.ThrowIfNull(progressReporter); + + progressReporter.StageName = "Loading Old Repository"; + + Uri origin; + string? oldReference; + string oldSha; + ValueTask deleteTask; + using (var oldRepo = await instance.RepositoryManager.LoadRepository(cancellationToken)) + { + if (oldRepo == null) + throw new JobException(ErrorCode.RepoMissing); + + origin = oldRepo.Origin; + oldSha = oldRepo.Head; + oldReference = oldRepo.Reference; + if (oldReference == Repository.NoReference) + oldReference = null; + + progressReporter.StageName = "Deleting Old Repository"; + deleteTask = instance.RepositoryManager.DeleteRepository(cancellationToken); + } + + await deleteTask; + progressReporter.ReportProgress(0.1); + IRepository newRepo; + try + { + newRepo = await instance.RepositoryManager.CloneRepository( + origin, + oldReference, + currentModel.AccessUser, + currentModel.AccessToken, + progressReporter.CreateSection("Cloning New Repository", 0.8), + true, // TODO: Make configurable maybe... + cancellationToken) + ?? throw new JobException("A race condition occurred while recloning the repository. Somehow, it was fully cloned instantly after being deleted!"); // I'll take lines of code that should never be hit for $10k + } + catch (Exception ex) when (ex is not JobException) + { + logger.LogWarning("Reclone failed, clearing credentials!"); + + // need to clear credentials here + await databaseContextFactory.UseContextTaskReturn(context => + { + context.RepositorySettings.Attach(currentModel); + currentModel.AccessUser = null; + currentModel.AccessToken = null; + return context.Save(CancellationToken.None); // DCT: Must always run + }); + + throw; + } + + using (newRepo) + await newRepo.CheckoutObject( + oldSha, + currentModel.AccessUser, + currentModel.AccessToken, + false, + oldReference != null, + progressReporter.CreateSection("Checking out previous Detached Commit", 0.1), + cancellationToken); + } } } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 5d4d686aaf..735b9efa83 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -223,6 +223,42 @@ namespace Tgstation.Server.Host.Controllers return Accepted(api); } + /// + /// Delete the repository. + /// + /// The for the operation. + /// A resulting in the of the operation. + /// Job to delete the repository created successfully. + /// The database entity for the requested instance could not be retrieved. The instance was likely detached. + [HttpPatch] + [TgsAuthorize(RepositoryRights.Reclone)] + [ProducesResponseType(typeof(RepositoryResponse), 202)] + [ProducesResponseType(typeof(ErrorMessageResponse), 410)] + public async ValueTask Reclone(CancellationToken cancellationToken) + { + var currentModel = await DatabaseContext + .RepositorySettings + .AsQueryable() + .Where(x => x.InstanceId == Instance.Id) + .FirstOrDefaultAsync(cancellationToken); + + if (currentModel == default) + return this.Gone(); + + Logger.LogInformation("Instance {instanceId} repository reclone initiated by user {userId}", Instance.Id, AuthenticationContext.User.Require(x => x.Id)); + + var repositoryUpdater = CreateRepositoryUpdateService(currentModel); + + var job = Job.Create(JobCode.RepositoryReclone, AuthenticationContext.User, Instance); + var api = currentModel.ToApi(); + await jobManager.RegisterOperation( + job, + (core, databaseContextFactory, paramJob, progressReporter, ct) => repositoryUpdater.RepositoryRecloneJob(core, databaseContextFactory, progressReporter, ct), + cancellationToken); + api.ActiveJob = job.ToApi(); + return Accepted(api); + } + /// /// Get the repository's status. /// @@ -443,17 +479,12 @@ namespace Tgstation.Server.Host.Controllers var job = Job.Create(JobCode.RepositoryUpdate, AuthenticationContext.User, Instance, RepositoryRights.CancelPendingChanges); job.Description = description; - var repositoryUpdater = new RepositoryUpdateService( - model, - currentModel, - AuthenticationContext.User, - loggerFactory.CreateLogger(), - Instance.Require(x => x.Id)); + var repositoryUpdater = CreateRepositoryUpdateService(currentModel); // Time to access git, do it in a job await jobManager.RegisterOperation( job, - repositoryUpdater.RepositoryUpdateJob, + (instance, databaseContextFactory, _, progressReporter, jobToken) => repositoryUpdater.RepositoryUpdateJob(model, instance, databaseContextFactory, progressReporter, jobToken), cancellationToken); api.ActiveJob = job.ToApi(); @@ -494,5 +525,17 @@ namespace Tgstation.Server.Host.Controllers cancellationToken); return needsDbUpdate; } + + /// + /// Creates a . + /// + /// The current . + /// A new . + RepositoryUpdateService CreateRepositoryUpdateService(RepositorySettings currentModel) + => new( + currentModel, + AuthenticationContext.User, + loggerFactory.CreateLogger(), + Instance.Require(x => x.Id)); } } diff --git a/tests/Tgstation.Server.Tests/Live/Instance/RepositoryTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/RepositoryTest.cs index 0d6ecce864..86fe4ac911 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/RepositoryTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/RepositoryTest.cs @@ -116,6 +116,8 @@ namespace Tgstation.Server.Tests.Live.Instance await ApiAssert.ThrowsException(() => Checkout(new RepositoryUpdateRequest { Reference = "master", CheckoutSha = "286bb75" }, false, false, cancellationToken), ErrorCode.RepoMismatchShaAndReference); var updated = await Checkout(new RepositoryUpdateRequest { CheckoutSha = "286bb75" }, false, false, cancellationToken); + await RecloneTest(cancellationToken); + // Fake SHA updated = await Checkout(new RepositoryUpdateRequest { CheckoutSha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, true, false, cancellationToken); @@ -142,6 +144,23 @@ namespace Tgstation.Server.Tests.Live.Instance await TestMergeTests(updated, prNumber, cancellationToken); } + async ValueTask RecloneTest(CancellationToken cancellationToken) + { + var initialState = await repositoryClient.Read(cancellationToken); + Assert.IsNotNull(initialState.Reference); + Assert.IsNotNull(initialState.RevisionInformation); + Assert.IsNotNull(initialState.RevisionInformation.CommitSha); + Assert.IsNotNull(initialState.RevisionInformation.OriginCommitSha); + + var reclone = await repositoryClient.Reclone(cancellationToken); + await WaitForJob(reclone.ActiveJob, 70, false, null, cancellationToken); + + var newState = await repositoryClient.Read(cancellationToken); + Assert.AreEqual(initialState.Reference, newState.Reference); + Assert.AreEqual(initialState.RevisionInformation.CommitSha, newState.RevisionInformation.CommitSha); + Assert.AreEqual(initialState.RevisionInformation.OriginCommitSha, newState.RevisionInformation.OriginCommitSha); + } + async ValueTask Checkout(RepositoryUpdateRequest updated, bool expectFailure, bool isRef, CancellationToken cancellationToken) { var newRef = isRef ? updated.Reference : updated.CheckoutSha; diff --git a/tests/Tgstation.Server.Tests/TestRepository.cs b/tests/Tgstation.Server.Tests/TestRepository.cs index e73731de40..59b4783994 100644 --- a/tests/Tgstation.Server.Tests/TestRepository.cs +++ b/tests/Tgstation.Server.Tests/TestRepository.cs @@ -45,7 +45,7 @@ namespace Tgstation.Server.Tests () => { }); const string StartSha = "af4da8beb9f9b374b04a3cc4d65acca662e8cc1a"; - await repo.CheckoutObject(StartSha, null, null, true, new JobProgressReporter(Mock.Of>(), null, (stage, progress) => { }), CancellationToken.None); + await repo.CheckoutObject(StartSha, null, null, true, false, new JobProgressReporter(Mock.Of>(), null, (stage, progress) => { }), CancellationToken.None); Assert.AreEqual(Host.Components.Repository.Repository.NoReference, repo.Reference); From 5b4719c1285b7a7e1a0d0a8a8b17ea7b809573c9 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 10 Aug 2024 21:44:58 -0400 Subject: [PATCH 5/8] Fix a race condition in OpenDreamInstaller --- .../Components/Engine/OpenDreamInstaller.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs index f99d70bfa4..608d3addb5 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs @@ -157,6 +157,8 @@ namespace Tgstation.Server.Host.Components.Engine { Logger.LogTrace("OD repo seems to already exist, attempting load and fetch..."); repo = await repositoryManager.LoadRepository(cancellationToken); + if (repo == null) + throw new JobException("Can't load OpenDream repository! Please delete cache from disk!"); await repo!.FetchOrigin( progressSection1, From 77a20cce750264c30500803f9a922a1d8a90d4ce Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 10 Aug 2024 23:56:25 -0400 Subject: [PATCH 6/8] Implement CPU/Memory sampling Closes #611 --- .../Models/Response/DreamDaemonResponse.cs | 12 ++++ .../Components/DreamDaemonClient.cs | 7 ++- .../Components/IDreamDaemonClient.cs | 3 +- .../Components/Session/SessionController.cs | 7 +++ .../Components/Watchdog/IWatchdog.cs | 8 +++ .../Components/Watchdog/WatchdogBase.cs | 11 ++++ .../Controllers/DreamDaemonController.cs | 28 +++++++-- .../System/IProcessBase.cs | 16 ++++- src/Tgstation.Server.Host/System/Process.cs | 30 ++++++++++ .../System/ProcessExecutor.cs | 11 ++++ .../System/TestPosixSignalHandler.cs | 1 + .../Live/Instance/DeploymentTest.cs | 2 +- .../Live/Instance/WatchdogTest.cs | 59 +++++++++++-------- .../Live/TestLiveServer.cs | 13 ++-- .../TestSystemInteraction.cs | 2 + tests/Tgstation.Server.Tests/TestVersions.cs | 1 + 16 files changed, 172 insertions(+), 39 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs b/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs index cad5094d1a..8f54e19c62 100644 --- a/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs @@ -59,5 +59,17 @@ namespace Tgstation.Server.Api.Models.Response /// [ResponseOptions] public bool? CurrentAllowWebclient { get; set; } + + /// + /// The amount of RAM in use by the game server in bytes. + /// + [ResponseOptions] + public long? ImmediateMemoryUsage { get; set; } + + /// + /// The CPU usage of the game server on a scale from 0-1. + /// + [ResponseOptions] + public double? ImmediateCpuUsage { get; set; } } } diff --git a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs index cf4eb3ea87..3d5e8a6786 100644 --- a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs @@ -43,7 +43,12 @@ namespace Tgstation.Server.Client.Components public ValueTask Restart(CancellationToken cancellationToken) => apiClient.Patch(Routes.DreamDaemon, instance.Id!.Value, cancellationToken); /// - public ValueTask Read(CancellationToken cancellationToken) => apiClient.Read(Routes.DreamDaemon, instance.Id!.Value, cancellationToken); + public ValueTask Read(ulong? profileMs, CancellationToken cancellationToken) => apiClient.Read( + profileMs.HasValue + ? $"{Routes.DreamDaemon}?profileMs={profileMs.Value}" + : Routes.DreamDaemon, + instance.Id!.Value, + cancellationToken); /// public ValueTask Update(DreamDaemonRequest dreamDaemon, CancellationToken cancellationToken) => apiClient.Update(Routes.DreamDaemon, dreamDaemon ?? throw new ArgumentNullException(nameof(dreamDaemon)), instance.Id!.Value, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs index 7a7a6ead7c..50c416b97c 100644 --- a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs @@ -14,9 +14,10 @@ namespace Tgstation.Server.Client.Components /// /// Get the represented by the . /// + /// The amount of time to spend performance profiling. /// The for the operation. /// A resulting in the information. - ValueTask Read(CancellationToken cancellationToken); + ValueTask Read(ulong? profileMs = null, CancellationToken cancellationToken = default); /// /// Start . diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 10556ca9fc..ed1509cf56 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -121,6 +121,9 @@ namespace Tgstation.Server.Host.Components.Session /// public FifoSemaphore TopicSendSemaphore { get; } + /// + public long MemoryUsage => process.MemoryUsage; + /// /// The for the . /// @@ -513,6 +516,10 @@ namespace Tgstation.Server.Host.Components.Session return process.CreateDump(outputFile, minidump, cancellationToken); } + /// + public ValueTask GetCpuUsage(TimeSpan waitingWindow, CancellationToken cancellationToken) + => process.GetCpuUsage(waitingWindow, cancellationToken); + /// /// The for . /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 96c6c9ccf9..1decbb915a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -102,5 +102,13 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the operation. /// A resulting in if the broadcast succeeded., otherwise. ValueTask Broadcast(string message, CancellationToken cancellationToken); + + /// + /// Profile memory and CPU usage of the running game server. + /// + /// The duration to profile the CPU usage of the game server for. + /// The for the operation. + /// A resulting in the performance metrics or if the server is offline. + Task<(long MemoryUsage, double CpuUsage)?> PerformanceProfile(TimeSpan timeSpan, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 28156db6e5..3a9b402901 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -289,6 +289,17 @@ namespace Tgstation.Server.Host.Components.Watchdog return true; } + /// + public async Task<(long MemoryUsage, double CpuUsage)?> PerformanceProfile(TimeSpan timeSpan, CancellationToken cancellationToken) + { + var controller = GetActiveController(); + if (controller == null) + return null; + + var cpuUsage = await controller.GetCpuUsage(timeSpan, cancellationToken); + return (controller.MemoryUsage, CpuUsage: cpuUsage); + } + /// public async ValueTask HandleChatCommand(string commandName, string arguments, ChatUser sender, CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index d60fda967d..3c9854a1a3 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -98,6 +98,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Get the watchdog status. /// + /// The amount of time to spend profiling game server CPU performance. /// The for the operation. /// A resulting in the of the operation. /// Read information successfully. @@ -106,7 +107,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)] [ProducesResponseType(typeof(DreamDaemonResponse), 200)] [ProducesResponseType(typeof(ErrorMessageResponse), 410)] - public ValueTask Read(CancellationToken cancellationToken) => ReadImpl(null, false, cancellationToken); + public ValueTask Read([FromQuery] ulong? profileMs, CancellationToken cancellationToken) => ReadImpl(null, profileMs, false, cancellationToken); /// /// Stops the Watchdog if it's running. @@ -252,7 +253,7 @@ namespace Tgstation.Server.Host.Controllers else if ((oldSoftRestart && model.SoftRestart == false) || (oldSoftShutdown && model.SoftShutdown == false)) await watchdog.ResetRebootState(cancellationToken); - return await ReadImpl(current, rebootRequired, cancellationToken); + return await ReadImpl(current, null, rebootRequired, cancellationToken); }); } #pragma warning restore CA1506 @@ -311,18 +312,25 @@ namespace Tgstation.Server.Host.Controllers }); /// - /// Implementation of . + /// Implementation of . /// /// The to operate on if any. + /// The amount of time to spend profiling game server CPU performance. /// If there was a settings change made that forced a switch to . /// The for the operation. /// A resulting in the of the operation. - ValueTask ReadImpl(DreamDaemonSettings? settings, bool knownForcedReboot, CancellationToken cancellationToken) +#pragma warning disable CA1502 // TODO: Decomplexify + ValueTask ReadImpl(DreamDaemonSettings? settings, ulong? profileMs, bool knownForcedReboot, CancellationToken cancellationToken) +#pragma warning restore CA1502 => WithComponentInstance(async instance => { var dd = instance.Watchdog; - var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadMetadata) != 0; + + Task<(long MemoryUsage, double CpuUsage)?>? profilingTask = null; + if (metadata && profileMs.HasValue && dd.Status == WatchdogStatus.Online) + profilingTask = dd.PerformanceProfile(TimeSpan.FromMilliseconds(profileMs.Value), cancellationToken); + var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadRevision) != 0; if (settings == null) @@ -385,6 +393,16 @@ namespace Tgstation.Server.Host.Controllers result.LogOutput = settings.LogOutput; result.MapThreads = settings.MapThreads; result.Minidumps = settings.Minidumps; + + if (profilingTask != null) + { + var profile = await profilingTask; + if (profile.HasValue) + { + result.ImmediateMemoryUsage = profile.Value.MemoryUsage; + result.ImmediateCpuUsage = profile.Value.CpuUsage; + } + } } if (revision) diff --git a/src/Tgstation.Server.Host/System/IProcessBase.cs b/src/Tgstation.Server.Host/System/IProcessBase.cs index 92f6bdb159..f4a613b62c 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 @@ -13,6 +14,19 @@ namespace Tgstation.Server.Host.System /// Task Lifetime { get; } + /// + /// Gets the process' memory usage in bytes. + /// + long MemoryUsage { get; } + + /// + /// Measures the 's CPU use percentage over a period of time. + /// + /// The to measure the percentage over. + /// The for the operation. + /// A ranging from 0-1 representing the percentage of the process' CPU time that was measured. + ValueTask GetCpuUsage(TimeSpan waitingWindow, CancellationToken cancellationToken); + /// /// Set's the owned to a non-normal value. /// diff --git a/src/Tgstation.Server.Host/System/Process.cs b/src/Tgstation.Server.Host/System/Process.cs index 896195944c..2224ee3aac 100644 --- a/src/Tgstation.Server.Host/System/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Win32.SafeHandles; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.System { @@ -22,11 +23,19 @@ namespace Tgstation.Server.Host.System /// public Task Lifetime { get; } + /// + public long MemoryUsage => handle.VirtualMemorySize64; + /// /// The for the . /// readonly IProcessFeatures processFeatures; + /// + /// The for the . + /// + readonly IAsyncDelayer asyncDelayer; + /// /// The for the . /// @@ -62,6 +71,7 @@ namespace Tgstation.Server.Host.System /// Initializes a new instance of the class. /// /// The value of . + /// The value of . /// The value of . /// The override value of . /// The value of . @@ -69,6 +79,7 @@ namespace Tgstation.Server.Host.System /// If was NOT just created. public Process( IProcessFeatures processFeatures, + IAsyncDelayer asyncDelayer, global::System.Diagnostics.Process handle, CancellationTokenSource? readerCts, Task? readTask, @@ -84,6 +95,7 @@ namespace Tgstation.Server.Host.System cancellationTokenSource = readerCts ?? new CancellationTokenSource(); this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.readTask = readTask; @@ -233,6 +245,24 @@ namespace Tgstation.Server.Host.System return processFeatures.CreateDump(handle, outputFile, minidump, cancellationToken); } + /// + public async ValueTask GetCpuUsage(TimeSpan waitingWindow, CancellationToken cancellationToken) + { + var startCpuUsage = handle.TotalProcessorTime; + var stopwatch = Stopwatch.StartNew(); + await asyncDelayer.Delay(waitingWindow, cancellationToken); + + var endCpuUsage = handle.TotalProcessorTime; + var totalElapsedTime = stopwatch.Elapsed; + + var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; + var totalMsPassed = totalElapsedTime.TotalMilliseconds; + + var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); + + return cpuUsageTotal; + } + /// /// Attaches a log message to the process' exit event. /// diff --git a/src/Tgstation.Server.Host/System/ProcessExecutor.cs b/src/Tgstation.Server.Host/System/ProcessExecutor.cs index 01fe2c00a4..67d77d3600 100644 --- a/src/Tgstation.Server.Host/System/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/ProcessExecutor.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.System { @@ -32,6 +33,11 @@ namespace Tgstation.Server.Host.System /// readonly IIOManager ioManager; + /// + /// The for the . + /// + readonly IAsyncDelayer asyncDelayer; + /// /// The for the . /// @@ -64,16 +70,19 @@ namespace Tgstation.Server.Host.System /// /// The value of . /// The value of . + /// The value of . /// The value of . /// The value of . public ProcessExecutor( IProcessFeatures processFeatures, IIOManager ioManager, + IAsyncDelayer asyncDelayer, ILogger logger, ILoggerFactory loggerFactory) { this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } @@ -195,6 +204,7 @@ namespace Tgstation.Server.Host.System var process = new Process( processFeatures, + asyncDelayer, handle, disposeCts, readTask, @@ -354,6 +364,7 @@ namespace Tgstation.Server.Host.System var pid = handle.Id; return new Process( processFeatures, + asyncDelayer, handle, null, null, diff --git a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs index dd0f5fd5fb..6b01eec4f0 100644 --- a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs +++ b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs @@ -61,6 +61,7 @@ namespace Tgstation.Server.Host.System.Tests new DefaultIOManager(), loggerFactory.CreateLogger()), Mock.Of(), + Mock.Of(), loggerFactory.CreateLogger(), loggerFactory); await using var subProc = await processExecutor diff --git a/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs index 1b20cd560c..ddab5d3aef 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs @@ -230,7 +230,7 @@ namespace Tgstation.Server.Tests.Live.Instance async Task TestVisibilityPermission(CancellationToken cancellationToken) { - var updatedDD = await dreamDaemonClient.Read(cancellationToken); + var updatedDD = await dreamDaemonClient.Read(null, cancellationToken); Assert.AreEqual(DreamDaemonVisibility.Public, updatedDD.Visibility); updatedDD = await dreamDaemonClient.Update(new DreamDaemonRequest { diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 26bba79305..e4b53d0307 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -150,6 +150,8 @@ namespace Tgstation.Server.Tests.Live.Instance }, cancellationToken); Assert.AreEqual(47, updated.OpenDreamTopicPort); + Assert.IsFalse(updated.ImmediateCpuUsage.HasValue); + Assert.IsFalse(updated.ImmediateMemoryUsage.HasValue); } catch (ConflictException ex) when (ex.ErrorCode == ErrorCode.PortNotAvailable) { @@ -266,7 +268,7 @@ namespace Tgstation.Server.Tests.Live.Instance do { await Task.Delay(TimeSpan.FromSeconds(1), tempToken); - currentStatus = await instanceClient.DreamDaemon.Read(tempToken); + currentStatus = await instanceClient.DreamDaemon.Read(null, tempToken); } while (currentStatus.Status != WatchdogStatus.Offline); } @@ -358,7 +360,7 @@ namespace Tgstation.Server.Tests.Live.Instance // This one fucks with the access_identifer, run it in isolation await WhiteBoxValidateBridgeRequestLimitAndTestChunking(cancellationToken); - var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); + var ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); await CheckDMApiFail(ddInfo.ActiveCompileJob, cancellationToken); var deleteJob = await deleteJobTask; @@ -613,7 +615,7 @@ namespace Tgstation.Server.Tests.Live.Instance await WaitForJob(startJob, 40, false, null, cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); ValidateSessionId(daemonStatus, true); @@ -714,17 +716,19 @@ namespace Tgstation.Server.Tests.Live.Instance await WaitForJob(startJob, 40, false, null, cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); ValidateSessionId(daemonStatus, true); await CheckDDPriority(); Assert.AreEqual(false, daemonStatus.SoftRestart); Assert.AreEqual(false, daemonStatus.SoftShutdown); + Assert.IsFalse(daemonStatus.ImmediateMemoryUsage.HasValue); + Assert.IsFalse(daemonStatus.ImmediateCpuUsage.HasValue); await GracefulWatchdogShutdown(cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); Assert.IsFalse(daemonStatus.SessionId.HasValue); await ExpectGameDirectoryCount(1, cancellationToken); @@ -845,6 +849,7 @@ namespace Tgstation.Server.Tests.Live.Instance ? new WindowsProcessFeatures(Mock.Of>()) : new PosixProcessFeatures(new Lazy(() => executor), new DefaultIOManager(), Mock.Of>()), Mock.Of(), + Mock.Of(), Mock.Of>(), LoggerFactory.Create(x => { })); await using var ourProcessHandler = executor @@ -894,7 +899,7 @@ namespace Tgstation.Server.Tests.Live.Instance var timeout = 20; do { - ddStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + ddStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(1U, ddStatus.HealthCheckSeconds.Value); if (ddStatus.Status.Value == WatchdogStatus.Offline) { @@ -1114,11 +1119,11 @@ namespace Tgstation.Server.Tests.Live.Instance // - Injects a custom bridge handler into the bridge registrar and makes the test hack into the DMAPI and change its access_identifier async Task WhiteBoxChatCommandTest(CancellationToken cancellationToken) { - var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); + var ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); for (int i = 0; ddInfo.Status != WatchdogStatus.Online && i < 15; ++i) { await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); - ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); + ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); } Assert.AreEqual(WatchdogStatus.Online, ddInfo.Status); @@ -1169,7 +1174,7 @@ namespace Tgstation.Server.Tests.Live.Instance var endTime = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(5); - ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); + ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); await CheckDMApiFail(ddInfo.ActiveCompileJob, cancellationToken); CheckEmbedsTest(embedsResponse, startTime, endTime); @@ -1270,7 +1275,7 @@ namespace Tgstation.Server.Tests.Live.Instance await instanceClient.DreamDaemon.Shutdown(cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } @@ -1317,7 +1322,7 @@ namespace Tgstation.Server.Tests.Live.Instance await instanceClient.DreamDaemon.Shutdown(cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } @@ -1330,7 +1335,7 @@ namespace Tgstation.Server.Tests.Live.Instance var currentByondVersion = await instanceClient.Engine.ActiveVersion(cancellationToken); Assert.AreNotEqual(versionToInstall, currentByondVersion.EngineVersion); - var initialStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + var initialStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); var startJob = await StartDD(cancellationToken); @@ -1360,11 +1365,17 @@ namespace Tgstation.Server.Tests.Live.Instance await DeployTestDme(DmeName, DreamDaemonSecurity.Safe, true, cancellationToken); - var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + var daemonStatus = await instanceClient.DreamDaemon.Read(500, cancellationToken); Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); Assert.IsNotNull(daemonStatus.ActiveCompileJob); ValidateSessionId(daemonStatus, true); + Assert.IsTrue(daemonStatus.ImmediateCpuUsage.HasValue); + Assert.IsTrue(daemonStatus.ImmediateMemoryUsage.HasValue); + + // Assert.AreNotEqual(0, daemonStatus.ImmediateCpuUsage.Value); sleep_offline cucks this check + Assert.AreNotEqual(0, daemonStatus.ImmediateMemoryUsage.Value); + Assert.AreEqual(initialStatus.ActiveCompileJob.Id, daemonStatus.ActiveCompileJob.Id); var newerCompileJob = daemonStatus.StagedCompileJob; Assert.AreNotEqual(daemonStatus.ActiveCompileJob.EngineVersion, newerCompileJob.EngineVersion); @@ -1383,7 +1394,7 @@ namespace Tgstation.Server.Tests.Live.Instance await instanceClient.DreamDaemon.Shutdown(cancellationToken); await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); } @@ -1416,7 +1427,7 @@ namespace Tgstation.Server.Tests.Live.Instance KillDD(firstTime); firstTime = false; await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); } while (daemonStatus.Status == WatchdogStatus.Online); Assert.AreEqual(WatchdogStatus.Restoring, daemonStatus.Status); @@ -1425,7 +1436,7 @@ namespace Tgstation.Server.Tests.Live.Instance do { KillDD(false); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); } while (daemonStatus.Status == WatchdogStatus.Online || daemonStatus.Status == WatchdogStatus.Restoring); Assert.AreEqual(WatchdogStatus.DelayedRestart, daemonStatus.Status); @@ -1460,7 +1471,7 @@ namespace Tgstation.Server.Tests.Live.Instance => TellWorldToReboot2(instanceClient, instanceManager, topicClient, FindTopicPort(), waitForOnlineIfRestoring || testVersion.Engine.Value == EngineType.OpenDream, cancellationToken, source); public static async Task TellWorldToReboot2(IInstanceClient instanceClient, IInstanceManager instanceManager, ITopicClient topicClient, ushort topicPort, bool waitForOnlineIfRestoring, CancellationToken cancellationToken, [CallerLineNumber]int source = 0, [CallerFilePath]string path = null) { - var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + var daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.IsNotNull(daemonStatus.StagedCompileJob); var initialSession = daemonStatus.ActiveCompileJob; @@ -1478,7 +1489,7 @@ namespace Tgstation.Server.Tests.Live.Instance do { await Task.Delay(TimeSpan.FromSeconds(1), tempToken); - daemonStatus = await instanceClient.DreamDaemon.Read(tempToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, tempToken); } while (initialSession.Id == daemonStatus.ActiveCompileJob.Id); } @@ -1488,7 +1499,7 @@ namespace Tgstation.Server.Tests.Live.Instance do { await Task.Delay(TimeSpan.FromSeconds(1), tempToken); - daemonStatus = await instanceClient.DreamDaemon.Read(tempToken); + daemonStatus = await instanceClient.DreamDaemon.Read(null, tempToken); } while (daemonStatus.Status == WatchdogStatus.Restoring); } @@ -1531,7 +1542,7 @@ namespace Tgstation.Server.Tests.Live.Instance for (var i = 0; i < 10; ++i) await Task.Yield(); - var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); + var ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); var targetJob = ddInfo.StagedCompileJob ?? ddInfo.ActiveCompileJob; Assert.IsNotNull(targetJob); if (requireApi) @@ -1549,14 +1560,14 @@ namespace Tgstation.Server.Tests.Live.Instance SoftShutdown = true }, cancellationToken); - var newStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + var newStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.IsTrue(newStatus.SoftShutdown.Value || newStatus.Status.Value == WatchdogStatus.Offline); var timeout = 40; do { await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken); - var ddStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + var ddStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); if (ddStatus.Status.Value == WatchdogStatus.Offline) break; @@ -1584,7 +1595,7 @@ namespace Tgstation.Server.Tests.Live.Instance if (!checkLogs) return; - var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + var daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); if (daemonStatus.Status != WatchdogStatus.Offline || !daemonStatus.LogOutput.Value) return; @@ -1607,7 +1618,7 @@ namespace Tgstation.Server.Tests.Live.Instance cancellationToken); Assert.IsNotNull(result); Assert.AreEqual("all gucci", result.StringData); - await CheckDMApiFail((await instanceClient.DreamDaemon.Read(cancellationToken)).ActiveCompileJob, cancellationToken); + await CheckDMApiFail((await instanceClient.DreamDaemon.Read(null, cancellationToken)).ActiveCompileJob, cancellationToken); } } } diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index b34b14f066..87383f216b 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -1095,6 +1095,7 @@ namespace Tgstation.Server.Tests.Live ? new WindowsProcessFeatures(loggerFactory.CreateLogger()) : new PosixProcessFeatures(new Lazy(() => processExecutor), ioManager, loggerFactory.CreateLogger()), ioManager, + Mock.Of(), loggerFactory.CreateLogger(), loggerFactory); @@ -1197,7 +1198,7 @@ namespace Tgstation.Server.Tests.Live { await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); - var status = await instanceClient.DreamDaemon.Read(cancellationToken); + var status = await instanceClient.DreamDaemon.Read(null, cancellationToken); if (updated) { @@ -1545,7 +1546,7 @@ namespace Tgstation.Server.Tests.Live if (openDreamOnly) return; - var dd = await instanceClient.DreamDaemon.Read(cancellationToken); + var dd = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); Assert.IsNotNull(dd.StagedCompileJob); Assert.AreNotEqual(dd.StagedCompileJob.Id, dd.ActiveCompileJob.Id); @@ -1628,7 +1629,7 @@ namespace Tgstation.Server.Tests.Live await jrt.WaitForJob(job, 130, job.Description.Contains("Reconnect chat bot") ? null : false, null, cancellationToken); } - var dd = await instanceClient.DreamDaemon.Read(cancellationToken); + var dd = await instanceClient.DreamDaemon.Read(500, cancellationToken); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); Assert.IsNotNull(dd.StagedCompileJob); Assert.AreNotEqual(dd.StagedCompileJob.Id, dd.ActiveCompileJob.Id); @@ -1712,7 +1713,7 @@ namespace Tgstation.Server.Tests.Live var instanceClient = adminClient.Instances.CreateClient(instance); await WaitForInitialJobs(instanceClient); - var dd = await instanceClient.DreamDaemon.Read(cancellationToken); + var dd = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); @@ -1720,7 +1721,7 @@ namespace Tgstation.Server.Tests.Live await using var wdt = new WatchdogTest(edgeVersion, instanceClient, GetInstanceManager(), (ushort)server.ApiUrl.Port, server.HighPriorityDreamDaemon, mainDDPort.Value, server.UsingBasicWatchdog); await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken); - dd = await instanceClient.DreamDaemon.Read(cancellationToken); + dd = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(dd.StagedCompileJob.Job.Id, compileJob.Id); expectedCompileJobId = compileJob.Id.Value; @@ -1755,7 +1756,7 @@ namespace Tgstation.Server.Tests.Live var instanceClient = adminClient.Instances.CreateClient(instance); await WaitForInitialJobs(instanceClient); - var currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); + var currentDD = await instanceClient.DreamDaemon.Read(null, cancellationToken); Assert.AreEqual(expectedCompileJobId, currentDD.ActiveCompileJob.Id.Value); Assert.AreEqual(WatchdogStatus.Online, currentDD.Status); Assert.AreEqual(expectedStaged, currentDD.StagedCompileJob.Job.Id.Value); diff --git a/tests/Tgstation.Server.Tests/TestSystemInteraction.cs b/tests/Tgstation.Server.Tests/TestSystemInteraction.cs index e7c8e28962..da66aa3b7a 100644 --- a/tests/Tgstation.Server.Tests/TestSystemInteraction.cs +++ b/tests/Tgstation.Server.Tests/TestSystemInteraction.cs @@ -25,6 +25,7 @@ namespace Tgstation.Server.Tests var processExecutor = new ProcessExecutor( Mock.Of(), new DefaultIOManager(), + Mock.Of(), Mock.Of>(), loggerFactory); @@ -53,6 +54,7 @@ namespace Tgstation.Server.Tests var processExecutor = new ProcessExecutor( Mock.Of(), new DefaultIOManager(), + Mock.Of(), loggerFactory.CreateLogger(), loggerFactory); diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs index adadd4bfcb..7afbd15a71 100644 --- a/tests/Tgstation.Server.Tests/TestVersions.cs +++ b/tests/Tgstation.Server.Tests/TestVersions.cs @@ -211,6 +211,7 @@ namespace Tgstation.Server.Tests new DefaultIOManager(), loggerFactory.CreateLogger()), Mock.Of(), + Mock.Of(), loggerFactory.CreateLogger(), loggerFactory); From 79fcf9c117bfb89343ad95cfa422c675fc377412 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 10 Aug 2024 23:56:38 -0400 Subject: [PATCH 7/8] Version bump to 6.9.0 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index c947fc3c5c..15258f13a1 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.8.0 + 6.9.0 5.2.0 10.7.0 7.0.0 From 7edf66440fa7f580bc04639bd15c437e4ee7144f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 11 Aug 2024 07:20:27 -0400 Subject: [PATCH 8/8] Remove CPU tracking. It's a nothing stat on multi-core systems --- .../Models/Response/DreamDaemonResponse.cs | 6 -- .../Components/DreamDaemonClient.cs | 6 +- .../Components/IDreamDaemonClient.cs | 3 +- .../Components/Session/SessionController.cs | 4 -- .../Components/Watchdog/IWatchdog.cs | 13 ++--- .../Components/Watchdog/WatchdogBase.cs | 14 +---- .../Controllers/DreamDaemonController.cs | 26 ++------- .../System/IProcessBase.cs | 11 +--- src/Tgstation.Server.Host/System/Process.cs | 27 --------- .../System/ProcessExecutor.cs | 11 ---- .../System/TestPosixSignalHandler.cs | 1 - .../Live/Instance/DeploymentTest.cs | 2 +- .../Live/Instance/WatchdogTest.cs | 57 +++++++++---------- .../Live/TestLiveServer.cs | 13 ++--- .../TestSystemInteraction.cs | 2 - tests/Tgstation.Server.Tests/TestVersions.cs | 1 - 16 files changed, 50 insertions(+), 147 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs b/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs index 8f54e19c62..d0b86cdaa4 100644 --- a/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs @@ -65,11 +65,5 @@ namespace Tgstation.Server.Api.Models.Response /// [ResponseOptions] public long? ImmediateMemoryUsage { get; set; } - - /// - /// The CPU usage of the game server on a scale from 0-1. - /// - [ResponseOptions] - public double? ImmediateCpuUsage { get; set; } } } diff --git a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs index 3d5e8a6786..1ea2dccb3b 100644 --- a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs @@ -43,10 +43,8 @@ namespace Tgstation.Server.Client.Components public ValueTask Restart(CancellationToken cancellationToken) => apiClient.Patch(Routes.DreamDaemon, instance.Id!.Value, cancellationToken); /// - public ValueTask Read(ulong? profileMs, CancellationToken cancellationToken) => apiClient.Read( - profileMs.HasValue - ? $"{Routes.DreamDaemon}?profileMs={profileMs.Value}" - : Routes.DreamDaemon, + public ValueTask Read(CancellationToken cancellationToken) => apiClient.Read( + Routes.DreamDaemon, instance.Id!.Value, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs index 50c416b97c..823c8d6a0c 100644 --- a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs @@ -14,10 +14,9 @@ namespace Tgstation.Server.Client.Components /// /// Get the represented by the . /// - /// The amount of time to spend performance profiling. /// The for the operation. /// A resulting in the information. - ValueTask Read(ulong? profileMs = null, CancellationToken cancellationToken = default); + ValueTask Read(CancellationToken cancellationToken = default); /// /// Start . diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index ed1509cf56..6880ed7f6d 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -516,10 +516,6 @@ namespace Tgstation.Server.Host.Components.Session return process.CreateDump(outputFile, minidump, cancellationToken); } - /// - public ValueTask GetCpuUsage(TimeSpan waitingWindow, CancellationToken cancellationToken) - => process.GetCpuUsage(waitingWindow, cancellationToken); - /// /// The for . /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 1decbb915a..2220c56932 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 /// WatchdogStatus Status { get; } + /// + /// Gets the memory usage of the game server in bytes. + /// + long? MemoryUsage { get; } + /// /// If the alpha server is the active server. /// @@ -102,13 +107,5 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the operation. /// A resulting in if the broadcast succeeded., otherwise. ValueTask Broadcast(string message, CancellationToken cancellationToken); - - /// - /// Profile memory and CPU usage of the running game server. - /// - /// The duration to profile the CPU usage of the game server for. - /// The for the operation. - /// A resulting in the performance metrics or if the server is offline. - Task<(long MemoryUsage, double CpuUsage)?> PerformanceProfile(TimeSpan timeSpan, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 3a9b402901..5d9326ce7d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -50,6 +50,9 @@ namespace Tgstation.Server.Host.Components.Watchdog } } + /// + public long? MemoryUsage => GetActiveController()?.MemoryUsage; + /// public abstract bool AlphaIsActive { get; } @@ -289,17 +292,6 @@ namespace Tgstation.Server.Host.Components.Watchdog return true; } - /// - public async Task<(long MemoryUsage, double CpuUsage)?> PerformanceProfile(TimeSpan timeSpan, CancellationToken cancellationToken) - { - var controller = GetActiveController(); - if (controller == null) - return null; - - var cpuUsage = await controller.GetCpuUsage(timeSpan, cancellationToken); - return (controller.MemoryUsage, CpuUsage: cpuUsage); - } - /// public async ValueTask HandleChatCommand(string commandName, string arguments, ChatUser sender, CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 3c9854a1a3..79e03ebb84 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -98,7 +98,6 @@ namespace Tgstation.Server.Host.Controllers /// /// Get the watchdog status. /// - /// The amount of time to spend profiling game server CPU performance. /// The for the operation. /// A resulting in the of the operation. /// Read information successfully. @@ -107,7 +106,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)] [ProducesResponseType(typeof(DreamDaemonResponse), 200)] [ProducesResponseType(typeof(ErrorMessageResponse), 410)] - public ValueTask Read([FromQuery] ulong? profileMs, CancellationToken cancellationToken) => ReadImpl(null, profileMs, false, cancellationToken); + public ValueTask Read(CancellationToken cancellationToken) => ReadImpl(null, false, cancellationToken); /// /// Stops the Watchdog if it's running. @@ -253,7 +252,7 @@ namespace Tgstation.Server.Host.Controllers else if ((oldSoftRestart && model.SoftRestart == false) || (oldSoftShutdown && model.SoftShutdown == false)) await watchdog.ResetRebootState(cancellationToken); - return await ReadImpl(current, null, rebootRequired, cancellationToken); + return await ReadImpl(current, rebootRequired, cancellationToken); }); } #pragma warning restore CA1506 @@ -312,25 +311,19 @@ namespace Tgstation.Server.Host.Controllers }); /// - /// Implementation of . + /// Implementation of . /// /// The to operate on if any. - /// The amount of time to spend profiling game server CPU performance. /// If there was a settings change made that forced a switch to . /// The for the operation. /// A resulting in the of the operation. #pragma warning disable CA1502 // TODO: Decomplexify - ValueTask ReadImpl(DreamDaemonSettings? settings, ulong? profileMs, bool knownForcedReboot, CancellationToken cancellationToken) + ValueTask ReadImpl(DreamDaemonSettings? settings, bool knownForcedReboot, CancellationToken cancellationToken) #pragma warning restore CA1502 => WithComponentInstance(async instance => { var dd = instance.Watchdog; var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadMetadata) != 0; - - Task<(long MemoryUsage, double CpuUsage)?>? profilingTask = null; - if (metadata && profileMs.HasValue && dd.Status == WatchdogStatus.Online) - profilingTask = dd.PerformanceProfile(TimeSpan.FromMilliseconds(profileMs.Value), cancellationToken); - var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadRevision) != 0; if (settings == null) @@ -380,6 +373,7 @@ namespace Tgstation.Server.Host.Controllers result.Visibility = settings.Visibility!.Value; result.SoftRestart = rstate == RebootState.Restart; result.SoftShutdown = rstate == RebootState.Shutdown; + result.ImmediateMemoryUsage = dd.MemoryUsage; if (rstate == RebootState.Normal && knownForcedReboot) result.SoftRestart = true; @@ -393,16 +387,6 @@ namespace Tgstation.Server.Host.Controllers result.LogOutput = settings.LogOutput; result.MapThreads = settings.MapThreads; result.Minidumps = settings.Minidumps; - - if (profilingTask != null) - { - var profile = await profilingTask; - if (profile.HasValue) - { - result.ImmediateMemoryUsage = profile.Value.MemoryUsage; - result.ImmediateCpuUsage = profile.Value.CpuUsage; - } - } } if (revision) diff --git a/src/Tgstation.Server.Host/System/IProcessBase.cs b/src/Tgstation.Server.Host/System/IProcessBase.cs index f4a613b62c..f34d5e6597 100644 --- a/src/Tgstation.Server.Host/System/IProcessBase.cs +++ b/src/Tgstation.Server.Host/System/IProcessBase.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.System @@ -19,14 +18,6 @@ namespace Tgstation.Server.Host.System /// long MemoryUsage { get; } - /// - /// Measures the 's CPU use percentage over a period of time. - /// - /// The to measure the percentage over. - /// The for the operation. - /// A ranging from 0-1 representing the percentage of the process' CPU time that was measured. - ValueTask GetCpuUsage(TimeSpan waitingWindow, CancellationToken cancellationToken); - /// /// Set's the owned to a non-normal value. /// diff --git a/src/Tgstation.Server.Host/System/Process.cs b/src/Tgstation.Server.Host/System/Process.cs index 2224ee3aac..64c92bf89b 100644 --- a/src/Tgstation.Server.Host/System/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Win32.SafeHandles; using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.System { @@ -31,11 +30,6 @@ namespace Tgstation.Server.Host.System /// readonly IProcessFeatures processFeatures; - /// - /// The for the . - /// - readonly IAsyncDelayer asyncDelayer; - /// /// The for the . /// @@ -71,7 +65,6 @@ namespace Tgstation.Server.Host.System /// Initializes a new instance of the class. /// /// The value of . - /// The value of . /// The value of . /// The override value of . /// The value of . @@ -79,7 +72,6 @@ namespace Tgstation.Server.Host.System /// If was NOT just created. public Process( IProcessFeatures processFeatures, - IAsyncDelayer asyncDelayer, global::System.Diagnostics.Process handle, CancellationTokenSource? readerCts, Task? readTask, @@ -95,7 +87,6 @@ namespace Tgstation.Server.Host.System cancellationTokenSource = readerCts ?? new CancellationTokenSource(); this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures)); - this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.readTask = readTask; @@ -245,24 +236,6 @@ namespace Tgstation.Server.Host.System return processFeatures.CreateDump(handle, outputFile, minidump, cancellationToken); } - /// - public async ValueTask GetCpuUsage(TimeSpan waitingWindow, CancellationToken cancellationToken) - { - var startCpuUsage = handle.TotalProcessorTime; - var stopwatch = Stopwatch.StartNew(); - await asyncDelayer.Delay(waitingWindow, cancellationToken); - - var endCpuUsage = handle.TotalProcessorTime; - var totalElapsedTime = stopwatch.Elapsed; - - var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; - var totalMsPassed = totalElapsedTime.TotalMilliseconds; - - var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); - - return cpuUsageTotal; - } - /// /// Attaches a log message to the process' exit event. /// diff --git a/src/Tgstation.Server.Host/System/ProcessExecutor.cs b/src/Tgstation.Server.Host/System/ProcessExecutor.cs index 67d77d3600..01fe2c00a4 100644 --- a/src/Tgstation.Server.Host/System/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/ProcessExecutor.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.System { @@ -33,11 +32,6 @@ namespace Tgstation.Server.Host.System /// readonly IIOManager ioManager; - /// - /// The for the . - /// - readonly IAsyncDelayer asyncDelayer; - /// /// The for the . /// @@ -70,19 +64,16 @@ namespace Tgstation.Server.Host.System /// /// The value of . /// The value of . - /// The value of . /// The value of . /// The value of . public ProcessExecutor( IProcessFeatures processFeatures, IIOManager ioManager, - IAsyncDelayer asyncDelayer, ILogger logger, ILoggerFactory loggerFactory) { this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); - this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } @@ -204,7 +195,6 @@ namespace Tgstation.Server.Host.System var process = new Process( processFeatures, - asyncDelayer, handle, disposeCts, readTask, @@ -364,7 +354,6 @@ namespace Tgstation.Server.Host.System var pid = handle.Id; return new Process( processFeatures, - asyncDelayer, handle, null, null, diff --git a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs index 6b01eec4f0..dd0f5fd5fb 100644 --- a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs +++ b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs @@ -61,7 +61,6 @@ namespace Tgstation.Server.Host.System.Tests new DefaultIOManager(), loggerFactory.CreateLogger()), Mock.Of(), - Mock.Of(), loggerFactory.CreateLogger(), loggerFactory); await using var subProc = await processExecutor diff --git a/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs index ddab5d3aef..1b20cd560c 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs @@ -230,7 +230,7 @@ namespace Tgstation.Server.Tests.Live.Instance async Task TestVisibilityPermission(CancellationToken cancellationToken) { - var updatedDD = await dreamDaemonClient.Read(null, cancellationToken); + var updatedDD = await dreamDaemonClient.Read(cancellationToken); Assert.AreEqual(DreamDaemonVisibility.Public, updatedDD.Visibility); updatedDD = await dreamDaemonClient.Update(new DreamDaemonRequest { diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index e4b53d0307..08f436b26d 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -150,7 +150,6 @@ namespace Tgstation.Server.Tests.Live.Instance }, cancellationToken); Assert.AreEqual(47, updated.OpenDreamTopicPort); - Assert.IsFalse(updated.ImmediateCpuUsage.HasValue); Assert.IsFalse(updated.ImmediateMemoryUsage.HasValue); } catch (ConflictException ex) when (ex.ErrorCode == ErrorCode.PortNotAvailable) @@ -268,7 +267,7 @@ namespace Tgstation.Server.Tests.Live.Instance do { await Task.Delay(TimeSpan.FromSeconds(1), tempToken); - currentStatus = await instanceClient.DreamDaemon.Read(null, tempToken); + currentStatus = await instanceClient.DreamDaemon.Read(tempToken); } while (currentStatus.Status != WatchdogStatus.Offline); } @@ -360,7 +359,7 @@ namespace Tgstation.Server.Tests.Live.Instance // This one fucks with the access_identifer, run it in isolation await WhiteBoxValidateBridgeRequestLimitAndTestChunking(cancellationToken); - var ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); await CheckDMApiFail(ddInfo.ActiveCompileJob, cancellationToken); var deleteJob = await deleteJobTask; @@ -615,7 +614,7 @@ namespace Tgstation.Server.Tests.Live.Instance await WaitForJob(startJob, 40, false, null, cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); ValidateSessionId(daemonStatus, true); @@ -716,19 +715,19 @@ namespace Tgstation.Server.Tests.Live.Instance await WaitForJob(startJob, 40, false, null, cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); ValidateSessionId(daemonStatus, true); await CheckDDPriority(); Assert.AreEqual(false, daemonStatus.SoftRestart); Assert.AreEqual(false, daemonStatus.SoftShutdown); - Assert.IsFalse(daemonStatus.ImmediateMemoryUsage.HasValue); - Assert.IsFalse(daemonStatus.ImmediateCpuUsage.HasValue); + Assert.IsTrue(daemonStatus.ImmediateMemoryUsage.HasValue); + Assert.AreNotEqual(0, daemonStatus.ImmediateMemoryUsage.Value); await GracefulWatchdogShutdown(cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); Assert.IsFalse(daemonStatus.SessionId.HasValue); await ExpectGameDirectoryCount(1, cancellationToken); @@ -849,7 +848,6 @@ namespace Tgstation.Server.Tests.Live.Instance ? new WindowsProcessFeatures(Mock.Of>()) : new PosixProcessFeatures(new Lazy(() => executor), new DefaultIOManager(), Mock.Of>()), Mock.Of(), - Mock.Of(), Mock.Of>(), LoggerFactory.Create(x => { })); await using var ourProcessHandler = executor @@ -899,7 +897,7 @@ namespace Tgstation.Server.Tests.Live.Instance var timeout = 20; do { - ddStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + ddStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(1U, ddStatus.HealthCheckSeconds.Value); if (ddStatus.Status.Value == WatchdogStatus.Offline) { @@ -1119,11 +1117,11 @@ namespace Tgstation.Server.Tests.Live.Instance // - Injects a custom bridge handler into the bridge registrar and makes the test hack into the DMAPI and change its access_identifier async Task WhiteBoxChatCommandTest(CancellationToken cancellationToken) { - var ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); for (int i = 0; ddInfo.Status != WatchdogStatus.Online && i < 15; ++i) { await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); - ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); + ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); } Assert.AreEqual(WatchdogStatus.Online, ddInfo.Status); @@ -1174,7 +1172,7 @@ namespace Tgstation.Server.Tests.Live.Instance var endTime = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(5); - ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); + ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); await CheckDMApiFail(ddInfo.ActiveCompileJob, cancellationToken); CheckEmbedsTest(embedsResponse, startTime, endTime); @@ -1275,7 +1273,7 @@ namespace Tgstation.Server.Tests.Live.Instance await instanceClient.DreamDaemon.Shutdown(cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } @@ -1322,7 +1320,7 @@ namespace Tgstation.Server.Tests.Live.Instance await instanceClient.DreamDaemon.Shutdown(cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } @@ -1335,7 +1333,7 @@ namespace Tgstation.Server.Tests.Live.Instance var currentByondVersion = await instanceClient.Engine.ActiveVersion(cancellationToken); Assert.AreNotEqual(versionToInstall, currentByondVersion.EngineVersion); - var initialStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var initialStatus = await instanceClient.DreamDaemon.Read(cancellationToken); var startJob = await StartDD(cancellationToken); @@ -1365,15 +1363,12 @@ namespace Tgstation.Server.Tests.Live.Instance await DeployTestDme(DmeName, DreamDaemonSecurity.Safe, true, cancellationToken); - var daemonStatus = await instanceClient.DreamDaemon.Read(500, cancellationToken); + var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); Assert.IsNotNull(daemonStatus.ActiveCompileJob); ValidateSessionId(daemonStatus, true); - Assert.IsTrue(daemonStatus.ImmediateCpuUsage.HasValue); Assert.IsTrue(daemonStatus.ImmediateMemoryUsage.HasValue); - - // Assert.AreNotEqual(0, daemonStatus.ImmediateCpuUsage.Value); sleep_offline cucks this check Assert.AreNotEqual(0, daemonStatus.ImmediateMemoryUsage.Value); Assert.AreEqual(initialStatus.ActiveCompileJob.Id, daemonStatus.ActiveCompileJob.Id); @@ -1394,7 +1389,7 @@ namespace Tgstation.Server.Tests.Live.Instance await instanceClient.DreamDaemon.Shutdown(cancellationToken); await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); } @@ -1427,7 +1422,7 @@ namespace Tgstation.Server.Tests.Live.Instance KillDD(firstTime); firstTime = false; await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); } while (daemonStatus.Status == WatchdogStatus.Online); Assert.AreEqual(WatchdogStatus.Restoring, daemonStatus.Status); @@ -1436,7 +1431,7 @@ namespace Tgstation.Server.Tests.Live.Instance do { KillDD(false); - daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); } while (daemonStatus.Status == WatchdogStatus.Online || daemonStatus.Status == WatchdogStatus.Restoring); Assert.AreEqual(WatchdogStatus.DelayedRestart, daemonStatus.Status); @@ -1471,7 +1466,7 @@ namespace Tgstation.Server.Tests.Live.Instance => TellWorldToReboot2(instanceClient, instanceManager, topicClient, FindTopicPort(), waitForOnlineIfRestoring || testVersion.Engine.Value == EngineType.OpenDream, cancellationToken, source); public static async Task TellWorldToReboot2(IInstanceClient instanceClient, IInstanceManager instanceManager, ITopicClient topicClient, ushort topicPort, bool waitForOnlineIfRestoring, CancellationToken cancellationToken, [CallerLineNumber]int source = 0, [CallerFilePath]string path = null) { - var daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.IsNotNull(daemonStatus.StagedCompileJob); var initialSession = daemonStatus.ActiveCompileJob; @@ -1489,7 +1484,7 @@ namespace Tgstation.Server.Tests.Live.Instance do { await Task.Delay(TimeSpan.FromSeconds(1), tempToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, tempToken); + daemonStatus = await instanceClient.DreamDaemon.Read(tempToken); } while (initialSession.Id == daemonStatus.ActiveCompileJob.Id); } @@ -1499,7 +1494,7 @@ namespace Tgstation.Server.Tests.Live.Instance do { await Task.Delay(TimeSpan.FromSeconds(1), tempToken); - daemonStatus = await instanceClient.DreamDaemon.Read(null, tempToken); + daemonStatus = await instanceClient.DreamDaemon.Read(tempToken); } while (daemonStatus.Status == WatchdogStatus.Restoring); } @@ -1542,7 +1537,7 @@ namespace Tgstation.Server.Tests.Live.Instance for (var i = 0; i < 10; ++i) await Task.Yield(); - var ddInfo = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken); var targetJob = ddInfo.StagedCompileJob ?? ddInfo.ActiveCompileJob; Assert.IsNotNull(targetJob); if (requireApi) @@ -1560,14 +1555,14 @@ namespace Tgstation.Server.Tests.Live.Instance SoftShutdown = true }, cancellationToken); - var newStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var newStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.IsTrue(newStatus.SoftShutdown.Value || newStatus.Status.Value == WatchdogStatus.Offline); var timeout = 40; do { await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken); - var ddStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var ddStatus = await instanceClient.DreamDaemon.Read(cancellationToken); if (ddStatus.Status.Value == WatchdogStatus.Offline) break; @@ -1595,7 +1590,7 @@ namespace Tgstation.Server.Tests.Live.Instance if (!checkLogs) return; - var daemonStatus = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); if (daemonStatus.Status != WatchdogStatus.Offline || !daemonStatus.LogOutput.Value) return; @@ -1618,7 +1613,7 @@ namespace Tgstation.Server.Tests.Live.Instance cancellationToken); Assert.IsNotNull(result); Assert.AreEqual("all gucci", result.StringData); - await CheckDMApiFail((await instanceClient.DreamDaemon.Read(null, cancellationToken)).ActiveCompileJob, cancellationToken); + await CheckDMApiFail((await instanceClient.DreamDaemon.Read(cancellationToken)).ActiveCompileJob, cancellationToken); } } } diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index 87383f216b..b34b14f066 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -1095,7 +1095,6 @@ namespace Tgstation.Server.Tests.Live ? new WindowsProcessFeatures(loggerFactory.CreateLogger()) : new PosixProcessFeatures(new Lazy(() => processExecutor), ioManager, loggerFactory.CreateLogger()), ioManager, - Mock.Of(), loggerFactory.CreateLogger(), loggerFactory); @@ -1198,7 +1197,7 @@ namespace Tgstation.Server.Tests.Live { await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); - var status = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var status = await instanceClient.DreamDaemon.Read(cancellationToken); if (updated) { @@ -1546,7 +1545,7 @@ namespace Tgstation.Server.Tests.Live if (openDreamOnly) return; - var dd = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var dd = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); Assert.IsNotNull(dd.StagedCompileJob); Assert.AreNotEqual(dd.StagedCompileJob.Id, dd.ActiveCompileJob.Id); @@ -1629,7 +1628,7 @@ namespace Tgstation.Server.Tests.Live await jrt.WaitForJob(job, 130, job.Description.Contains("Reconnect chat bot") ? null : false, null, cancellationToken); } - var dd = await instanceClient.DreamDaemon.Read(500, cancellationToken); + var dd = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); Assert.IsNotNull(dd.StagedCompileJob); Assert.AreNotEqual(dd.StagedCompileJob.Id, dd.ActiveCompileJob.Id); @@ -1713,7 +1712,7 @@ namespace Tgstation.Server.Tests.Live var instanceClient = adminClient.Instances.CreateClient(instance); await WaitForInitialJobs(instanceClient); - var dd = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var dd = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); @@ -1721,7 +1720,7 @@ namespace Tgstation.Server.Tests.Live await using var wdt = new WatchdogTest(edgeVersion, instanceClient, GetInstanceManager(), (ushort)server.ApiUrl.Port, server.HighPriorityDreamDaemon, mainDDPort.Value, server.UsingBasicWatchdog); await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken); - dd = await instanceClient.DreamDaemon.Read(null, cancellationToken); + dd = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(dd.StagedCompileJob.Job.Id, compileJob.Id); expectedCompileJobId = compileJob.Id.Value; @@ -1756,7 +1755,7 @@ namespace Tgstation.Server.Tests.Live var instanceClient = adminClient.Instances.CreateClient(instance); await WaitForInitialJobs(instanceClient); - var currentDD = await instanceClient.DreamDaemon.Read(null, cancellationToken); + var currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(expectedCompileJobId, currentDD.ActiveCompileJob.Id.Value); Assert.AreEqual(WatchdogStatus.Online, currentDD.Status); Assert.AreEqual(expectedStaged, currentDD.StagedCompileJob.Job.Id.Value); diff --git a/tests/Tgstation.Server.Tests/TestSystemInteraction.cs b/tests/Tgstation.Server.Tests/TestSystemInteraction.cs index da66aa3b7a..e7c8e28962 100644 --- a/tests/Tgstation.Server.Tests/TestSystemInteraction.cs +++ b/tests/Tgstation.Server.Tests/TestSystemInteraction.cs @@ -25,7 +25,6 @@ namespace Tgstation.Server.Tests var processExecutor = new ProcessExecutor( Mock.Of(), new DefaultIOManager(), - Mock.Of(), Mock.Of>(), loggerFactory); @@ -54,7 +53,6 @@ namespace Tgstation.Server.Tests var processExecutor = new ProcessExecutor( Mock.Of(), new DefaultIOManager(), - Mock.Of(), loggerFactory.CreateLogger(), loggerFactory); diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs index 7afbd15a71..adadd4bfcb 100644 --- a/tests/Tgstation.Server.Tests/TestVersions.cs +++ b/tests/Tgstation.Server.Tests/TestVersions.cs @@ -211,7 +211,6 @@ namespace Tgstation.Server.Tests new DefaultIOManager(), loggerFactory.CreateLogger()), Mock.Of(), - Mock.Of(), loggerFactory.CreateLogger(), loggerFactory);