Merge pull request #1786 from tgstation/1741-Minidumps [APIDeploy][NugetDeploy]

Minidumps
This commit is contained in:
Jordan Dominion
2024-02-02 19:20:30 -05:00
committed by GitHub
32 changed files with 4566 additions and 45 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
<ItemGroup>
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507">
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
+3 -3
View File
@@ -5,10 +5,10 @@
<PropertyGroup>
<TgsCoreVersion>6.1.5</TgsCoreVersion>
<TgsConfigVersion>5.1.0</TgsConfigVersion>
<TgsApiVersion>10.0.0</TgsApiVersion>
<TgsApiVersion>10.1.0</TgsApiVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>13.0.1</TgsApiLibraryVersion>
<TgsClientVersion>15.0.1</TgsClientVersion>
<TgsApiLibraryVersion>13.1.0</TgsApiLibraryVersion>
<TgsClientVersion>15.1.0</TgsClientVersion>
<TgsDmapiVersion>7.0.2</TgsDmapiVersion>
<TgsInteropVersion>5.8.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.4.1</TgsHostWatchdogVersion>
@@ -98,6 +98,13 @@ namespace Tgstation.Server.Api.Models.Internal
[ResponseOptions]
public uint? MapThreads { get; set; }
/// <summary>
/// If minidumps should be taken instead of full dumps.
/// </summary>
[Required]
[ResponseOptions]
public bool? Minidumps { get; set; }
/// <summary>
/// Check if we match a given set of <paramref name="otherParameters"/>. <see cref="StartupTimeout"/> is excluded.
/// </summary>
@@ -116,7 +123,7 @@ namespace Tgstation.Server.Api.Models.Internal
&& AdditionalParameters == otherParameters.AdditionalParameters
&& StartProfiler == otherParameters.StartProfiler
&& LogOutput == otherParameters.LogOutput
&& MapThreads == otherParameters.MapThreads; // We intentionally don't check StartupTimeout, health check seconds, or health check dump as they don't matter in terms of the watchdog
&& MapThreads == otherParameters.MapThreads; // We intentionally don't check StartupTimeout, Minidumps, health check seconds, or health check dump as they don't matter in terms of the watchdog
}
}
}
@@ -117,5 +117,10 @@ namespace Tgstation.Server.Api.Rights
/// User can use <see cref="Models.Request.DreamDaemonRequest.BroadcastMessage"/>.
/// </summary>
BroadcastMessage = 1 << 20,
/// <summary>
/// User can use <see cref="Models.Internal.DreamDaemonLaunchParameters.Minidumps"/>.
/// </summary>
SetMinidumps = 1 << 21,
}
}
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.0",
"version": "8.0.1",
"commands": [
"dotnet-ef"
]
@@ -487,12 +487,12 @@ namespace Tgstation.Server.Host.Components.Session
cancellationToken);
/// <inheritdoc />
public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken)
public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
{
if (engineLock.UseDotnetDump)
return dotnetDumpService.Dump(process, outputFile, cancellationToken);
return dotnetDumpService.Dump(process, outputFile, minidump, cancellationToken);
return process.CreateDump(outputFile, cancellationToken);
return process.CreateDump(outputFile, minidump, cancellationToken);
}
/// <summary>
@@ -1249,7 +1249,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
throw new JobException(ErrorCode.GameServerOffline);
Logger.LogInformation("Dumping session to {dumpFileName}...", dumpFileName);
await session.CreateDump(dumpFileName, cancellationToken);
await session.CreateDump(dumpFileName, ActiveLaunchParameters.Minidumps!.Value, cancellationToken);
}
}
}
@@ -149,7 +149,8 @@ namespace Tgstation.Server.Host.Controllers
| DreamDaemonRights.SetProfiler
| DreamDaemonRights.SetLogOutput
| DreamDaemonRights.SetMapThreads
| DreamDaemonRights.BroadcastMessage)]
| DreamDaemonRights.BroadcastMessage
| DreamDaemonRights.SetMinidumps)]
[ProducesResponseType(typeof(DreamDaemonResponse), 200)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
#pragma warning disable CA1502 // TODO: Decomplexify
@@ -222,7 +223,8 @@ namespace Tgstation.Server.Host.Controllers
|| CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters)
|| CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler)
|| CheckModified(x => x.LogOutput, DreamDaemonRights.SetLogOutput)
|| CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads))
|| CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads)
|| CheckModified(x => x.Minidumps, DreamDaemonRights.SetMinidumps))
return Forbid();
return await WithComponentInstance(
@@ -379,6 +381,7 @@ namespace Tgstation.Server.Host.Controllers
result.StartProfiler = settings.StartProfiler;
result.LogOutput = settings.LogOutput;
result.MapThreads = settings.MapThreads;
result.Minidumps = settings.Minidumps;
}
if (revision)
@@ -732,6 +732,7 @@ namespace Tgstation.Server.Host.Controllers
StartProfiler = false,
LogOutput = false,
MapThreads = 0,
Minidumps = true,
},
DreamMakerSettings = new DreamMakerSettings
{
@@ -375,22 +375,22 @@ namespace Tgstation.Server.Host.Database
/// <summary>
/// Used by unit tests to remind us to setup the correct MSSQL migration downgrades.
/// </summary>
internal static readonly Type MSLatestMigration = typeof(MSAddTopicPort);
internal static readonly Type MSLatestMigration = typeof(MSAddMinidumpsOption);
/// <summary>
/// Used by unit tests to remind us to setup the correct MYSQL migration downgrades.
/// </summary>
internal static readonly Type MYLatestMigration = typeof(MYAddTopicPort);
internal static readonly Type MYLatestMigration = typeof(MYAddMinidumpsOption);
/// <summary>
/// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades.
/// </summary>
internal static readonly Type PGLatestMigration = typeof(PGAddTopicPort);
internal static readonly Type PGLatestMigration = typeof(PGAddMinidumpsOption);
/// <summary>
/// Used by unit tests to remind us to setup the correct SQLite migration downgrades.
/// </summary>
internal static readonly Type SLLatestMigration = typeof(SLAddTopicPort);
internal static readonly Type SLLatestMigration = typeof(SLAddMinidumpsOption);
/// <inheritdoc />
#pragma warning disable CA1502 // Cyclomatic complexity
@@ -419,6 +419,16 @@ namespace Tgstation.Server.Host.Database
string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType));
if (targetVersion < new Version(6, 2, 0))
targetMigration = currentDatabaseType switch
{
DatabaseType.MySql => nameof(MYAddTopicPort),
DatabaseType.PostgresSql => nameof(PGAddTopicPort),
DatabaseType.SqlServer => nameof(MSAddTopicPort),
DatabaseType.Sqlite => nameof(SLAddTopicPort),
_ => BadDatabaseType(),
};
if (targetVersion < new Version(6, 0, 0))
targetMigration = currentDatabaseType switch
{
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
public partial class MSAddMinidumpsOption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
// This was originally minidumps on Linux and full dumps on Windows
var defaultValue = !new PlatformIdentifier().IsWindows;
migrationBuilder.AddColumn<bool>(
name: "Minidumps",
table: "DreamDaemonSettings",
type: "bit",
nullable: false,
defaultValue: defaultValue);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "Minidumps",
table: "DreamDaemonSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
public partial class MYAddMinidumpsOption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
// This was originally minidumps on Linux and full dumps on Windows
var defaultValue = !new PlatformIdentifier().IsWindows;
migrationBuilder.AddColumn<bool>(
name: "Minidumps",
table: "DreamDaemonSettings",
type: "tinyint(1)",
nullable: false,
defaultValue: defaultValue);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "Minidumps",
table: "DreamDaemonSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
public partial class PGAddMinidumpsOption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
// This was originally minidumps on Linux and full dumps on Windows
var defaultValue = !new PlatformIdentifier().IsWindows;
migrationBuilder.AddColumn<bool>(
name: "Minidumps",
table: "DreamDaemonSettings",
type: "boolean",
nullable: false,
defaultValue: defaultValue);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "Minidumps",
table: "DreamDaemonSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
public partial class SLAddMinidumpsOption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
// This was originally minidumps on Linux and full dumps on Windows
var defaultValue = !new PlatformIdentifier().IsWindows;
migrationBuilder.AddColumn<bool>(
name: "Minidumps",
table: "DreamDaemonSettings",
type: "INTEGER",
nullable: false,
defaultValue: defaultValue);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "Minidumps",
table: "DreamDaemonSettings");
}
}
}
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("ProductVersion", "8.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
@@ -219,6 +219,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired()
.HasColumnType("int unsigned");
b.Property<bool?>("Minidumps")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("smallint unsigned");
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("ProductVersion", "8.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -209,6 +209,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("MapThreads")
.HasColumnType("bigint");
b.Property<bool?>("Minidumps")
.IsRequired()
.HasColumnType("boolean");
b.Property<int>("Port")
.HasColumnType("integer");
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("ProductVersion", "8.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@@ -211,6 +211,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("MapThreads")
.HasColumnType("bigint");
b.Property<bool?>("Minidumps")
.IsRequired()
.HasColumnType("bit");
b.Property<int>("Port")
.HasColumnType("int");
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Database.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
@@ -201,6 +201,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("Minidumps")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("INTEGER");
@@ -26,7 +26,7 @@ namespace Tgstation.Server.Host.System
}
/// <inheritdoc />
public async ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken)
public async ValueTask Dump(IProcess process, string outputFile, bool minidump, CancellationToken cancellationToken)
{
// need to use an extra timeout here because if the process is truly deadlocked. A cooperative dump will hang forever
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
@@ -42,7 +42,13 @@ namespace Tgstation.Server.Host.System
var pid = process.Id;
logger.LogDebug("dotnet-dump requested for PID {pid}...", pid);
var client = new DiagnosticsClient(pid);
await client.WriteDumpAsync(DumpType.Full, outputFile, false, cts.Token);
await client.WriteDumpAsync(
minidump
? DumpType.Normal
: DumpType.Full,
outputFile,
false,
cts.Token);
}
}
}
@@ -13,8 +13,9 @@ namespace Tgstation.Server.Host.System
/// </summary>
/// <param name="process">The <see cref="IProcess"/> to dump.</param>
/// <param name="outputFile">The path to the output dump file.</param>
/// <param name="minidump">If a minidump should be taken as opposed to a full dump.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken);
ValueTask Dump(IProcess process, string outputFile, bool minidump, CancellationToken cancellationToken);
}
}
@@ -33,8 +33,9 @@ namespace Tgstation.Server.Host.System
/// Create a dump file of the process.
/// </summary>
/// <param name="outputFile">The full path to the output file.</param>
/// <param name="minidump">If a minidump should be taken as opposed to a full dump.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask CreateDump(string outputFile, CancellationToken cancellationToken);
ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken);
}
}
@@ -32,8 +32,9 @@ namespace Tgstation.Server.Host.System
/// </summary>
/// <param name="process">The <see cref="Process"/> to dump.</param>
/// <param name="outputFile">The full path to the output file.</param>
/// <param name="minidump">If a minidump should be taken as opposed to a full dump.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken);
ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken);
}
}
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.System
=> throw new NotSupportedException();
/// <inheritdoc />
public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(process);
ArgumentNullException.ThrowIfNull(outputFile);
@@ -91,7 +91,7 @@ namespace Tgstation.Server.Host.System
await using (var gcoreProc = lazyLoadedProcessExecutor.Value.LaunchProcess(
GCorePath,
Environment.CurrentDirectory,
$"-o {outputFile} {process.Id}",
$"{(!minidump ? "-a " : String.Empty)}-o {outputFile} {process.Id}",
readStandardHandles: true,
noShellExecute: true))
{
@@ -99,7 +99,7 @@ namespace Tgstation.Server.Host.System
exitCode = (await gcoreProc.Lifetime).Value;
output = await gcoreProc.GetCombinedOutput(cancellationToken);
logger.LogDebug("gcore output:{0}{1}", Environment.NewLine, output);
logger.LogDebug("gcore output:{newline}{output}", Environment.NewLine, output);
}
if (exitCode != 0)
+2 -2
View File
@@ -224,13 +224,13 @@ namespace Tgstation.Server.Host.System
}
/// <inheritdoc />
public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken)
public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(outputFile);
CheckDisposed();
logger.LogTrace("Dumping PID {pid} to {dumpFilePath}...", Id, outputFile);
return processFeatures.CreateDump(handle, outputFile, cancellationToken);
return processFeatures.CreateDump(handle, outputFile, minidump, cancellationToken);
}
/// <summary>
@@ -120,7 +120,7 @@ namespace Tgstation.Server.Host.System
}
/// <inheritdoc />
public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken)
{
try
{
@@ -137,15 +137,19 @@ namespace Tgstation.Server.Host.System
await Task.Factory.StartNew(
() =>
{
var flags = NativeMethods.MiniDumpType.WithHandleData
| NativeMethods.MiniDumpType.WithThreadInfo
| NativeMethods.MiniDumpType.WithUnloadedModules;
if (!minidump)
flags |= NativeMethods.MiniDumpType.WithDataSegs
| NativeMethods.MiniDumpType.WithFullMemory;
if (!NativeMethods.MiniDumpWriteDump(
process.Handle,
(uint)process.Id,
fileStream.SafeFileHandle,
NativeMethods.MiniDumpType.WithDataSegs
| NativeMethods.MiniDumpType.WithFullMemory
| NativeMethods.MiniDumpType.WithHandleData
| NativeMethods.MiniDumpType.WithThreadInfo
| NativeMethods.MiniDumpType.WithUnloadedModules,
flags,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero))
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../../build/SrcCommon.props" />
<PropertyGroup>
@@ -102,7 +102,7 @@
<!-- Usage: PostgresSQL ORM plugin -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<!-- Usage: GitHub.com interop -->
<PackageReference Include="Octokit" Version="9.1.1" />
<PackageReference Include="Octokit" Version="9.1.2" />
<!-- Usage: MYSQL/MariaDB ORM plugin -->
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.2" />
<!-- Usage: Discord interop -->
@@ -351,13 +351,11 @@ namespace Tgstation.Server.Tests.Live.Instance
var deleteJob = await deleteJobTask;
// And this freezes DD
await DumpTests(cancellationToken);
// And this freezes DD (also restarts it)
await DumpTests(false, cancellationToken);
await DumpTests(true, cancellationToken);
// Restart to unlock previous BYOND version
var restartJob = await instanceClient.DreamDaemon.Restart(cancellationToken);
await WaitForJob(deleteJob, 15, false, null, cancellationToken);
await WaitForJob(restartJob, 15, false, null, cancellationToken);
}
async ValueTask RegressionTest1550(CancellationToken cancellationToken)
@@ -519,14 +517,19 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.AreEqual("sent", topicRequestResult.StringData);
}
async Task DumpTests(CancellationToken cancellationToken)
async Task DumpTests(bool mini, CancellationToken cancellationToken)
{
System.Console.WriteLine("TEST: WATCHDOG DUMP TESTS");
var updated = await instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
Minidumps = mini,
}, cancellationToken);
Assert.AreEqual(mini, updated.Minidumps);
var dumpJob = await instanceClient.DreamDaemon.CreateDump(cancellationToken);
await WaitForJob(dumpJob, 30, false, null, cancellationToken);
var dumpFiles = Directory.GetFiles(Path.Combine(
instanceClient.Metadata.Path, "Diagnostics", "ProcessDumps"), "*.dmp");
instanceClient.Metadata.Path, "Diagnostics", "ProcessDumps"), testVersion.Engine == EngineType.OpenDream ? "*.net.dmp" : "*.dmp");
Assert.AreEqual(1, dumpFiles.Length);
File.Delete(dumpFiles.Single());
@@ -122,6 +122,7 @@ namespace Tgstation.Server.Tests
StartProfiler = false,
LogOutput = true,
MapThreads = 69,
Minidumps = true,
},
DreamMakerSettings = new Host.Models.DreamMakerSettings
{