From ab090d7e7ac3018d5ce4144e159c0bf6c8f4e75e Mon Sep 17 00:00:00 2001 From: Alex Williams-Ferreira Date: Mon, 20 Jul 2026 13:59:46 -0700 Subject: [PATCH] Report incomplete LAPACK runs as failures Propagate LAPACK cancellation before result capture as a workload failure without changing global component semantics. Validate the Windows build process and align profile metadata and tests with Windows ARM64 support. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f3bc03fa-5da5-4a69-94e1-d6adc0cbe5db --- .../LAPACKProfileTests.cs | 21 ++- .../LAPACK/LAPACKExecutorTests.cs | 35 ++++ .../LAPACK/LAPACKExecutor.cs | 176 ++++++++++-------- .../profiles/PERF-CPU-LAPACK.json | 4 +- 4 files changed, 150 insertions(+), 86 deletions(-) diff --git a/src/VirtualClient/VirtualClient.Actions.FunctionalTests/LAPACKProfileTests.cs b/src/VirtualClient/VirtualClient.Actions.FunctionalTests/LAPACKProfileTests.cs index 6aee5572bb..e5e7b405db 100644 --- a/src/VirtualClient/VirtualClient.Actions.FunctionalTests/LAPACKProfileTests.cs +++ b/src/VirtualClient/VirtualClient.Actions.FunctionalTests/LAPACKProfileTests.cs @@ -6,6 +6,7 @@ namespace VirtualClient.Actions using System; using System.Collections.Generic; using System.Linq; + using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -80,15 +81,19 @@ public async Task LAPACKWorkloadProfileInstallsTheExpectedDependenciesOnUnixPlat } [Test] - [TestCase("PERF-CPU-LAPACK.json")] - public async Task LAPACKWorkloadProfileExecutesTheExpectedWorkloadsOnWindowsPlatform(string profile) + [TestCase("PERF-CPU-LAPACK.json", Architecture.X64, "win-x64")] + [TestCase("PERF-CPU-LAPACK.json", Architecture.Arm64, "win-arm64")] + public async Task LAPACKWorkloadProfileExecutesTheExpectedWorkloadsOnWindowsPlatform( + string profile, + Architecture architecture, + string platformSpecificDirectory) { IEnumerable expectedCommands = this.GetProfileExpectedCommands(PlatformID.Win32NT); string[] expectedFiles = new string[] { - @"win-x64/cmakescript.sh", - @"win-x64/LapackTestScript.sh", @"win-x64/lapack_testing.py", - @"win-x64/TESTING/testing_results.txt" + $"{platformSpecificDirectory}/cmakescript.sh", + $"{platformSpecificDirectory}/LapackTestScript.sh", $"{platformSpecificDirectory}/lapack_testing.py", + $"{platformSpecificDirectory}/TESTING/testing_results.txt" }; string cygwinPath = this.mockFixture.PlatformSpecifics.Combine("C:", "tools", "cygwin"); @@ -96,7 +101,7 @@ public async Task LAPACKWorkloadProfileExecutesTheExpectedWorkloadsOnWindowsPlat // - Workload package is installed and exists. // - Workload binaries/executables exist on the file system. // - The workload generates valid results. - this.mockFixture.Setup(PlatformID.Win32NT); + this.mockFixture.Setup(PlatformID.Win32NT, architecture); this.mockFixture.SetupFile(cygwinPath); this.mockFixture.SetupPackage("lapack", expectedFiles: expectedFiles); this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDir) => @@ -105,7 +110,9 @@ public async Task LAPACKWorkloadProfileExecutesTheExpectedWorkloadsOnWindowsPlat if (arguments.Contains("LapackTestScript.sh", StringComparison.OrdinalIgnoreCase)) { process.StandardOutput.Append(TestDependencies.GetResourceFileContents("Results_LAPACK.txt")); - this.mockFixture.SetupPackage("lapack", expectedFiles: @"win-x64\TESTING\testing_results.txt"); + this.mockFixture.SetupPackage( + "lapack", + expectedFiles: $@"{platformSpecificDirectory}\TESTING\testing_results.txt"); } return process; diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/LAPACK/LAPACKExecutorTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/LAPACK/LAPACKExecutorTests.cs index 2bbeb7a794..8ecadc8db4 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/LAPACK/LAPACKExecutorTests.cs +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/LAPACK/LAPACKExecutorTests.cs @@ -6,6 +6,7 @@ namespace VirtualClient.Actions using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; @@ -237,6 +238,40 @@ public void LAPACKExecutorThrowsWhenTheResultsFileIsNotGenerated() } } + [Test] + public async Task LAPACKExecutorReportsCancellationAsFailed_WindowsArm64() + { + this.SetupTest(PlatformID.Win32NT, Architecture.Arm64); + using (CancellationTokenSource cancellationSource = new CancellationTokenSource()) + using (TestLAPACKExecutor executor = new TestLAPACKExecutor(this.mockFixture)) + { + this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDirectory) => + { + InMemoryProcess process = new InMemoryProcess(); + if (arguments.Contains("LapackTestScript.sh", StringComparison.OrdinalIgnoreCase)) + { + process.OnStart = () => + { + cancellationSource.Cancel(); + return true; + }; + } + + return process; + }; + + await ((VirtualClientComponent)executor).ExecuteAsync(cancellationSource.Token); + + var outcomeMessages = this.mockFixture.Logger.MessagesLogged($"{executor.TypeName}.SucceededOrFailed"); + Assert.AreEqual(1, outcomeMessages.Count()); + + EventContext outcomeContext = outcomeMessages.First().Item3 as EventContext; + Assert.IsNotNull(outcomeContext); + Assert.AreEqual("Failed", outcomeContext.Properties["metricName"]); + Assert.AreEqual(1, this.mockFixture.Logger.MessagesLogged($"{executor.TypeName}.ExecutionCancelled").Count()); + } + } + private class TestLAPACKExecutor : LAPACKExecutor { public TestLAPACKExecutor(MockFixture fixture) diff --git a/src/VirtualClient/VirtualClient.Actions/LAPACK/LAPACKExecutor.cs b/src/VirtualClient/VirtualClient.Actions/LAPACK/LAPACKExecutor.cs index a4416cc9c0..c3efff7d8d 100644 --- a/src/VirtualClient/VirtualClient.Actions/LAPACK/LAPACKExecutor.cs +++ b/src/VirtualClient/VirtualClient.Actions/LAPACK/LAPACKExecutor.cs @@ -80,24 +80,26 @@ protected override async Task InitializeAsync(EventContext telemetryContext, Can /// protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken) { - using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken)) + try { - if (this.Platform == PlatformID.Unix) + using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken)) { - // Run make to generate all object files for fortran subroutines. - await this.ExecuteCommandAsync("make", null, this.packageDirectory, cancellationToken) - .ConfigureAwait(false); - - // Delete results file that gets generated. - if (this.fileSystem.File.Exists(this.ResultsFilePath)) + if (this.Platform == PlatformID.Unix) { - await this.fileSystem.File.DeleteAsync(this.ResultsFilePath); - } + // Run make to generate all object files for fortran subroutines. + await this.ExecuteCommandAsync("make", null, this.packageDirectory, cancellationToken) + .ConfigureAwait(false); - using (IProcessProxy process = await this.ExecuteCommandAsync("bash", this.ScriptFilePath, this.packageDirectory, telemetryContext, cancellationToken, runElevated: true)) - { - if (!cancellationToken.IsCancellationRequested) + // Delete results file that gets generated. + if (this.fileSystem.File.Exists(this.ResultsFilePath)) + { + await this.fileSystem.File.DeleteAsync(this.ResultsFilePath); + } + + using (IProcessProxy process = await this.ExecuteCommandAsync("bash", this.ScriptFilePath, this.packageDirectory, telemetryContext, cancellationToken, runElevated: true)) { + cancellationToken.ThrowIfCancellationRequested(); + if (process.IsErrored()) { await this.LogProcessDetailsAsync(process, telemetryContext, "LAPACK", logToFile: true); @@ -107,22 +109,34 @@ await this.ExecuteCommandAsync("make", null, this.packageDirectory, cancellation await this.CaptureMetricsAsync(process, this.ResultsFilePath, telemetryContext, cancellationToken); } } - } - else if (this.Platform == PlatformID.Win32NT) - { - await this.ExecuteCygwinBashAsync("./cmakescript.sh", this.packageDirectory, this.cygwinPackageDirectory, telemetryContext, cancellationToken) - .ConfigureAwait(false); - - // Delete results file that gets generated. - if (this.fileSystem.File.Exists(this.ResultsFilePath)) + else if (this.Platform == PlatformID.Win32NT) { - await this.fileSystem.File.DeleteAsync(this.ResultsFilePath); - } + using (IProcessProxy process = await this.ExecuteCygwinBashAsync( + "./cmakescript.sh", + this.packageDirectory, + this.cygwinPackageDirectory, + telemetryContext, + cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); - using (IProcessProxy process = await this.ExecuteCygwinBashAsync("./LapackTestScript.sh", this.packageDirectory, this.cygwinPackageDirectory, telemetryContext, cancellationToken)) - { - if (!cancellationToken.IsCancellationRequested) + if (process.IsErrored()) + { + await this.LogProcessDetailsAsync(process, telemetryContext, "LAPACK", logToFile: true); + process.ThrowIfWorkloadFailed(); + } + } + + // Delete results file that gets generated. + if (this.fileSystem.File.Exists(this.ResultsFilePath)) { + await this.fileSystem.File.DeleteAsync(this.ResultsFilePath); + } + + using (IProcessProxy process = await this.ExecuteCygwinBashAsync("./LapackTestScript.sh", this.packageDirectory, this.cygwinPackageDirectory, telemetryContext, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (process.IsErrored()) { await this.LogProcessDetailsAsync(process, telemetryContext, "LAPACK", logToFile: true); @@ -134,73 +148,81 @@ await this.ExecuteCygwinBashAsync("./cmakescript.sh", this.packageDirectory, thi } } } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + EventContext cancelContext = telemetryContext.Clone() + .AddContext("executionCancelled", true); + + this.Logger.LogMessage($"{this.TypeName}.ExecutionCancelled", LogLevel.Information, cancelContext); + + throw new WorkloadException( + "The LAPACK workload did not complete before execution was cancelled.", + ErrorReason.WorkloadFailed); + } } - private async Task ExecuteCommandAsync(string pathToExe, string commandLineArguments, string workingDirectory, CancellationToken cancellationToken) + private Task ExecuteCommandAsync(string pathToExe, string commandLineArguments, string workingDirectory, CancellationToken cancellationToken) { - if (!cancellationToken.IsCancellationRequested) - { - this.Logger.LogTraceMessage($"Executing process '{pathToExe}' '{commandLineArguments}' at directory '{workingDirectory}'."); + cancellationToken.ThrowIfCancellationRequested(); + + this.Logger.LogTraceMessage($"Executing process '{pathToExe}' '{commandLineArguments}' at directory '{workingDirectory}'."); - EventContext telemetryContext = EventContext.Persisted() - .AddContext("command", pathToExe) - .AddContext("commandArguments", commandLineArguments); + EventContext telemetryContext = EventContext.Persisted() + .AddContext("command", pathToExe) + .AddContext("commandArguments", commandLineArguments); - await this.Logger.LogMessageAsync($"{nameof(LAPACKExecutor)}.ExecuteProcess", telemetryContext, async () => + return this.Logger.LogMessageAsync($"{nameof(LAPACKExecutor)}.ExecuteProcess", telemetryContext, async () => + { + using (IProcessProxy process = this.systemManagement.ProcessManager.CreateElevatedProcess(this.Platform, pathToExe, commandLineArguments, workingDirectory)) { - using (IProcessProxy process = this.systemManagement.ProcessManager.CreateElevatedProcess(this.Platform, pathToExe, commandLineArguments, workingDirectory)) - { - this.CleanupTasks.Add(() => process.SafeKill(this.Logger)); - await process.StartAndWaitAsync(cancellationToken).ConfigureAwait(); + this.CleanupTasks.Add(() => process.SafeKill(this.Logger)); + await process.StartAndWaitAsync(cancellationToken).ConfigureAwait(); - if (!cancellationToken.IsCancellationRequested) - { - this.LogProcessDetailsAsync(process, telemetryContext) - .ConfigureAwait(); + cancellationToken.ThrowIfCancellationRequested(); - process.ThrowIfErrored(errorReason: ErrorReason.WorkloadFailed); - } - } - }).ConfigureAwait(); - } + await this.LogProcessDetailsAsync(process, telemetryContext) + .ConfigureAwait(); + + process.ThrowIfErrored(errorReason: ErrorReason.WorkloadFailed); + } + }); } private async Task CaptureMetricsAsync(IProcessProxy process, string resultsFilePath, EventContext telemetryContext, CancellationToken cancellationToken) { - if (!cancellationToken.IsCancellationRequested) - { - this.MetadataContract.AddForScenario( - "LAPACK", - null, - toolVersion: null, - this.PackageName); + cancellationToken.ThrowIfCancellationRequested(); - this.MetadataContract.Apply(telemetryContext); + this.MetadataContract.AddForScenario( + "LAPACK", + null, + toolVersion: null, + this.PackageName); - if (!this.fileSystem.File.Exists(resultsFilePath)) - { - throw new WorkloadException( - $"The LAPACK results file was not found at path '{resultsFilePath}'.", - ErrorReason.WorkloadFailed); - } + this.MetadataContract.Apply(telemetryContext); - KeyValuePair results = await this.LoadResultsAsync(resultsFilePath, cancellationToken); - await this.LogProcessDetailsAsync(process, telemetryContext, "LAPACK", logToFile: true, results: results); - - LAPACKMetricsParser lapackParser = new LAPACKMetricsParser(results.Value); - IList metrics = lapackParser.Parse(); - - this.Logger.LogMetrics( - "LAPACK", - "Linear Algorithms", - process.StartTime, - process.ExitTime, - metrics, - null, - null, - this.Tags, - telemetryContext); + if (!this.fileSystem.File.Exists(resultsFilePath)) + { + throw new WorkloadException( + $"The LAPACK results file was not found at path '{resultsFilePath}'.", + ErrorReason.WorkloadFailed); } + + KeyValuePair results = await this.LoadResultsAsync(resultsFilePath, cancellationToken); + await this.LogProcessDetailsAsync(process, telemetryContext, "LAPACK", logToFile: true, results: results); + + LAPACKMetricsParser lapackParser = new LAPACKMetricsParser(results.Value); + IList metrics = lapackParser.Parse(); + + this.Logger.LogMetrics( + "LAPACK", + "Linear Algorithms", + process.StartTime, + process.ExitTime, + metrics, + null, + null, + this.Tags, + telemetryContext); } } } \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-LAPACK.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-LAPACK.json index 3681ca0434..95d8de8c29 100644 --- a/src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-LAPACK.json +++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-LAPACK.json @@ -2,8 +2,8 @@ "Description": "LAPACK Performance workload", "Metadata": { "RecommendedMinimumExecutionTime": "(4-cores)=02:00:00,(16-cores)=04:00:00,(64-cores)=10:00:00", - "SupportedPlatforms": "linux-x64,linux-arm64", - "SupportedOperatingSystems": "CBL-Mariner,CentOS,Debian,RedHat,Suse,Ubuntu" + "SupportedPlatforms": "linux-x64,linux-arm64,win-x64,win-arm64", + "SupportedOperatingSystems": "CBL-Mariner,CentOS,Debian,RedHat,Suse,Ubuntu,Windows" }, "Parameters": { "CompilerVersion": ""