-
Notifications
You must be signed in to change notification settings - Fork 301
[perf-improver] perf: add dotnet test server-mode scenario to performance runner #9486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Evangelink
wants to merge
2
commits into
main
Choose a base branch
from
perf-assist/dotnet-test-server-mode-scenario-467216da09463018
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+132
−0
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
test/Performance/MSTest.Performance.Runner/Steps/DotnetTestProcess.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
|
||
| using System.IO.Compression; | ||
| using System.Text.Json; | ||
|
|
||
| using Microsoft.Testing.TestInfrastructure; | ||
|
|
||
| namespace MSTest.Performance.Runner.Steps; | ||
|
|
||
| /// <summary> | ||
| /// Runs the test project via <c>dotnet test --no-build</c> to exercise the MTP | ||
| /// server-mode (JSON-RPC / named-pipe) path, and records wall-clock timing using | ||
| /// the same plain <see cref="Process"/> metrics as <see cref="PlainProcess"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// When <c>EnableMSTestRunner=true</c> (MTP native mode), <c>dotnet test</c> invokes the | ||
| /// compiled test host in server mode, passing <c>--server --protocol dotnet-test-protocol</c>. | ||
| /// The host then communicates results back via a named pipe / TCP socket rather than running | ||
| /// standalone. This exercises the serialisation, JSON-RPC framing, and pipe I/O paths that | ||
| /// the plain-process scenario does not cover. | ||
| /// </para> | ||
| /// <para> | ||
| /// <b>Measurement note:</b> <see cref="Process.TotalProcessorTime"/> reflects only the | ||
| /// <c>dotnet test</c> parent process; the spawned test-host child's CPU time is not included. | ||
| /// <see cref="Process.ExitTime"/> minus <see cref="Process.StartTime"/> (wall-clock) is the | ||
| /// primary metric and represents the end-to-end time a user observes when running | ||
| /// <c>dotnet test</c>. | ||
| /// </para> | ||
| /// </remarks> | ||
| internal class DotnetTestProcess : IStep<BuildArtifact, Files> | ||
| { | ||
| private static readonly string s_root = RootFinder.Find(); | ||
| private readonly string _reportFileName; | ||
| private readonly BuildConfiguration _buildConfiguration; | ||
| private readonly int _numberOfRun; | ||
| private readonly CompressionLevel _compressionLevel; | ||
|
|
||
| public string Description => "run dotnet test (MTP server mode)"; | ||
|
|
||
| public DotnetTestProcess(string reportFileName, BuildConfiguration buildConfiguration = BuildConfiguration.Debug, int numberOfRun = 3, CompressionLevel compressionLevel = CompressionLevel.Fastest) | ||
| { | ||
| _reportFileName = reportFileName; | ||
| _buildConfiguration = buildConfiguration; | ||
| _numberOfRun = numberOfRun; | ||
| _compressionLevel = compressionLevel; | ||
| } | ||
|
|
||
| public async Task<Files> ExecuteAsync(BuildArtifact payload, IContext context) | ||
| { | ||
| string dotnet = Path.Combine(s_root, ".dotnet", $"dotnet{Constants.ExecutableExtension}"); | ||
| string projectDir = payload.TestAsset.TargetAssetPath; | ||
|
|
||
| // Use the repo-local SDK consistently with the build step (DotnetMuxer). The | ||
| // configuration must match the one used by DotnetMuxer so that --no-build finds the | ||
| // binaries that were actually produced. WorkingDirectory is pinned to the test asset so | ||
| // relative outputs (TestResults, logs, temp files) stay inside the generated asset rather | ||
| // than polluting the runner's current directory between scenarios. | ||
| ProcessStartInfo psi = new(dotnet, $"test \"{projectDir}\" --no-build --configuration {_buildConfiguration}") | ||
| { | ||
| UseShellExecute = false, | ||
| RedirectStandardOutput = true, | ||
| RedirectStandardError = true, | ||
| WorkingDirectory = projectDir, | ||
| }; | ||
|
|
||
| psi.EnvironmentVariables["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1"; | ||
| psi.EnvironmentVariables["DOTNET_ROOT"] = Path.Combine(s_root, ".dotnet"); | ||
| psi.EnvironmentVariables["DOTNET_INSTALL_DIR"] = Path.Combine(s_root, ".dotnet"); | ||
| psi.EnvironmentVariables["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1"; | ||
| psi.EnvironmentVariables["DOTNET_MULTILEVEL_LOOKUP"] = "0"; | ||
|
|
||
| Console.WriteLine($"Process command: '{psi.FileName} {psi.Arguments.Trim()}' for {_numberOfRun} times"); | ||
|
|
||
| List<object> results = []; | ||
| for (int i = 0; i < _numberOfRun; i++) | ||
| { | ||
| using Process process = Process.Start(psi)!; | ||
| // Drain stdout/stderr asynchronously to prevent buffer deadlocks. | ||
| Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync(); | ||
| Task<string> stderrTask = process.StandardError.ReadToEndAsync(); | ||
| await process.WaitForExitAsync(); | ||
| await Task.WhenAll(stdoutTask, stderrTask); | ||
|
|
||
| // Fail fast on a non-zero exit code: `dotnet test` has many infrastructure failure | ||
| // modes (restore issues, SDK mismatch, missing build artefacts) that would otherwise | ||
| // record timings for an invalid run and silently corrupt the perf baseline. | ||
| if (process.ExitCode != 0) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"'dotnet test' exited with code {process.ExitCode}.{Environment.NewLine}" + | ||
| $"stdout:{Environment.NewLine}{await stdoutTask}{Environment.NewLine}" + | ||
| $"stderr:{Environment.NewLine}{await stderrTask}"); | ||
| } | ||
|
|
||
| var result = new | ||
| { | ||
| ElapsedTime = process.ExitTime - process.StartTime, | ||
| process.TotalProcessorTime, | ||
| Environment.ProcessorCount, | ||
| GC.GetGCMemoryInfo().TotalAvailableMemoryBytes, | ||
| }; | ||
|
|
||
| results.Add(result); | ||
| } | ||
|
|
||
| #pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances | ||
| await File.AppendAllTextAsync( | ||
| Path.Combine(Path.GetDirectoryName(payload.TestHost.FullName)!, "Result.json"), | ||
| JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true })); | ||
| #pragma warning restore CA1869 | ||
|
|
||
| string sample = Path.Combine(Path.GetTempPath(), _reportFileName); | ||
| File.Delete(sample); | ||
| Console.WriteLine($"Compressing to '{sample}'"); | ||
| ZipFile.CreateFromDirectory(payload.TestAsset.TargetAssetPath, sample, _compressionLevel, includeBaseDirectory: true); | ||
|
|
||
| return new Files([sample]); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.