From 391be8495a5dfa84db9f995c13cf483eb05884d7 Mon Sep 17 00:00:00 2001 From: saibulusu Date: Sat, 30 May 2026 11:42:08 -0700 Subject: [PATCH 1/4] Migrating dotnet cpu runtime from internal. --- .../DotNetRuntimeExecutorTests.cs | 155 +++++++ .../DotNetRuntimeMetricsParserTests.cs | 55 +++ .../Examples/DotNetRuntimeResultsExample.txt | 12 + .../DotNetRuntime/DotNetRuntimeExecutor.cs | 378 ++++++++++++++++++ .../DotNetRuntimeMetricsParser.cs | 94 +++++ .../profiles/PERF-CPU-DOTNETRUNTIME.json | 36 ++ .../dotnetruntime/dotnetruntime-profiles.md | 56 +++ .../dotnetruntime-supplemental.md | 72 ++++ .../dotnetruntime-workload-overview.docx | Bin 0 -> 17315 bytes .../workloads/dotnetruntime/dotnetruntime.md | 27 ++ 10 files changed, 885 insertions(+) create mode 100644 src/VirtualClient/VirtualClient.Actions.UnitTests/DotNetRuntime/DotNetRuntimeExecutorTests.cs create mode 100644 src/VirtualClient/VirtualClient.Actions.UnitTests/DotNetRuntime/DotNetRuntimeMetricsParserTests.cs create mode 100644 src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/DotNetRuntimeResultsExample.txt create mode 100644 src/VirtualClient/VirtualClient.Actions/DotNetRuntime/DotNetRuntimeExecutor.cs create mode 100644 src/VirtualClient/VirtualClient.Actions/DotNetRuntime/DotNetRuntimeMetricsParser.cs create mode 100644 src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-DOTNETRUNTIME.json create mode 100644 website/docs/workloads/dotnetruntime/dotnetruntime-profiles.md create mode 100644 website/docs/workloads/dotnetruntime/dotnetruntime-supplemental.md create mode 100644 website/docs/workloads/dotnetruntime/dotnetruntime-workload-overview.docx create mode 100644 website/docs/workloads/dotnetruntime/dotnetruntime.md diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/DotNetRuntime/DotNetRuntimeExecutorTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/DotNetRuntime/DotNetRuntimeExecutorTests.cs new file mode 100644 index 0000000000..574683eaa7 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/DotNetRuntime/DotNetRuntimeExecutorTests.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using AutoFixture; + using VirtualClient; + using VirtualClient.Common.Telemetry; + using VirtualClient.Contracts; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + + [TestFixture] + [Category("Unit")] + [Platform(Exclude = "Unix,Linux,MacOsX")] + public class DotNetRuntimeExecutorTests + { + private MockFixture fixture; + private DependencyPath mockPath; + private DependencyPath currentDirectoryPath; + private string rawString; + + [SetUp] + public void SetUpTests() + { + this.fixture = new MockFixture(); + this.fixture.Setup(PlatformID.Win32NT); + this.mockPath = this.fixture.Create(); + this.fixture.Parameters = new Dictionary + { + { "PackageName", "DotNetRuntime" } + }; + + this.SetupDefaultMockBehavior(); + } + + [Test] + public async Task DotNetRuntimeExecutorInitializesItsDependenciesAsExpected() + { + using (TestDotNetRuntimeExecutor executor = new TestDotNetRuntimeExecutor(this.fixture)) + { + Assert.IsNull(executor.ExecutablePath); + + await executor.InitializeAsync(EventContext.None, CancellationToken.None) + .ConfigureAwait(false); + + string expectedPath = this.fixture.PlatformSpecifics.Combine( + this.mockPath.Path, "win-x64", "dotnet.bat"); + Assert.AreEqual(expectedPath, executor.ExecutablePath); + } + } + + [Test] + public void DotNetRuntimeExecutorThrowsOnInitializationWhenTheWorkloadPackageIsNotFound() + { + this.fixture.PackageManager.OnGetPackage().ReturnsAsync(null as DependencyPath); + using (TestDotNetRuntimeExecutor executor = new TestDotNetRuntimeExecutor(this.fixture)) + { + DependencyException exception = Assert.ThrowsAsync( + () => executor.InitializeAsync(EventContext.None, CancellationToken.None)); + Assert.AreEqual(ErrorReason.WorkloadDependencyMissing, exception.Reason); + } + } + + [Test] + [Ignore("There is some kind of very unusual and difficult to determine anomaly that causes this method to fail to run due to a call to the IProcessProxy.Kill() method downstream.")] + public async Task DotNetRuntimeExecutorExecutesWorkloadAsExpected() + { + using (TestDotNetRuntimeExecutor executor = new TestDotNetRuntimeExecutor(this.fixture)) + { + string expectedFilePath = this.fixture.PlatformSpecifics.Combine(this.mockPath.Path, "runtimes", "win-x64", "dotnet.bat"); + int executed = 0; + this.fixture.ProcessManager.OnCreateProcess = (file, arguments, workingDirectory) => + { + executed++; + Assert.AreEqual(expectedFilePath, file); + return this.fixture.Process; + }; + + await executor.ExecuteAsync(EventContext.None, CancellationToken.None) + .ConfigureAwait(false); + + Assert.AreEqual(1, executed); + } + } + + [Test] + [Ignore("There is some kind of very unusual and difficult to determine anomaly that causes this method to fail to run due to a call to the IProcessProxy.Kill() method downstream.")] + public void DotNetRuntimeExecutorThrowsWorkloadExceptionWhenTheResultsFileIsNotGenerated() + { + using (TestDotNetRuntimeExecutor executor = new TestDotNetRuntimeExecutor(this.fixture)) + { + this.fixture.ProcessManager.OnCreateProcess = (file, arguments, workingDirectory) => + { + this.fixture.FileSystem.Setup(fe => fe.File.Exists(executor.ResultsFilePath)).Returns(false); + return this.fixture.Process; + }; + + WorkloadException exception = Assert.ThrowsAsync( + () => executor.ExecuteAsync(EventContext.None, CancellationToken.None)); + Assert.AreEqual(ErrorReason.WorkloadFailed, exception.Reason); + } + } + + private void SetupDefaultMockBehavior() + { + string currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + this.currentDirectoryPath = new DependencyPath("DotNetRuntime", currentDirectory); + string resultsPath = this.fixture.PlatformSpecifics.Combine(this.currentDirectoryPath.Path, "Examples", "DotNetRuntimeResultsExample.txt"); + this.rawString = File.ReadAllText(resultsPath); + this.fixture.FileSystem.Setup(fe => fe.File.Exists(It.IsAny())).Returns(true); + this.fixture.FileSystem.Setup(fe => fe.File.Exists(null)).Returns(false); + this.fixture.FileSystem.Setup(fc => fc.File.Copy(It.IsAny(), It.IsAny())); + this.fixture.Directory.Setup(d => d.Exists(It.IsAny())) + .Returns(true); + + this.fixture.FileSystem.Setup(rt => rt.File.ReadAllTextAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(this.rawString); + + this.fixture.PackageManager.OnGetPackage().ReturnsAsync(this.mockPath); + this.fixture.ProcessManager.OnCreateProcess = (command, arguments, directory) => this.fixture.Process; + } + + private class TestDotNetRuntimeExecutor : DotNetRuntimeExecutor + { + public TestDotNetRuntimeExecutor(MockFixture fixture) + : base(fixture.Dependencies, fixture.Parameters) + { + } + + public TestDotNetRuntimeExecutor(IServiceCollection dependencies, IDictionary parameters) + : base(dependencies, parameters) + { + } + + public new Task InitializeAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + return base.InitializeAsync(telemetryContext, cancellationToken); + } + + public new Task ExecuteAsync(EventContext context, CancellationToken cancellationToken) + { + this.InitializeAsync(context, cancellationToken).GetAwaiter().GetResult(); + return base.ExecuteAsync(context, cancellationToken); + } + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/DotNetRuntime/DotNetRuntimeMetricsParserTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/DotNetRuntime/DotNetRuntimeMetricsParserTests.cs new file mode 100644 index 0000000000..3292e16837 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/DotNetRuntime/DotNetRuntimeMetricsParserTests.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using VirtualClient.Contracts; +using NUnit.Framework; +using VirtualClient; + +namespace VirtualClient.Actions +{ + + [TestFixture] + [Category("Unit")] + internal class DotNetRuntimeMetricsParserUnitTests + { + private string rawText; + private DotNetRuntimeMetricsParser testParser; + + [SetUp] + public void Setup() + { + string workingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string outputPath = Path.Combine(workingDirectory, @"Examples", "DotNetRuntimeResultsExample.txt"); + this.rawText = File.ReadAllText(outputPath); + this.testParser = new DotNetRuntimeMetricsParser(this.rawText); + } + + [Test] + public void DotNetRuntimeParserVerifyThroughputResult() + { + this.testParser.Parse(); + this.testParser.ThroughputResult.PrintDataTableFormatted(); + Assert.AreEqual(4, this.testParser.ThroughputResult.Columns.Count); + } + + [Test] + public void DotNetRuntimeParserVerifyMetrics() + { + IList metrics = this.testParser.Parse(); + MetricAssert.Exists(metrics, "throughput", 11284.51, "bops"); + } + + [Test] + public void DotNetRuntimeParserThrowIfInvalidOutputFormat() + { + string workingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string IncorrectDotNetoutputPath =Path.Combine(workingDirectory, @"Examples", "IncorrectDotNetRuntimeResultsExample.txt"); + this.rawText = File.ReadAllText(IncorrectDotNetoutputPath); + this.testParser = new DotNetRuntimeMetricsParser(this.rawText); + SchemaException exception = Assert.Throws(() => this.testParser.Parse()); + StringAssert.Contains("The DotNetRuntime output file has incorrect format for parsing", exception.Message); + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/DotNetRuntimeResultsExample.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/DotNetRuntimeResultsExample.txt new file mode 100644 index 0000000000..886b074cd9 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/DotNetRuntimeResultsExample.txt @@ -0,0 +1,12 @@ +=============================================================================== +TOTALS FOR: COMPANY with 8 warehouses +........ .NET fixed throughput benchmark 1.0 Results (time in seconds) ........ + Count Total Min Max Avg Heap Space + New Order: 9142643 4568.99 0.000 ****** 0.000 total 0.0MB + Payment: 6101520 2572.06 0.000 ****** 0.000 used 0.0MB + OrderStatus: 691503 368.25 0.000 39.781 0.001 + Delivery: 671169 3068.53 0.000 ****** 0.005 + Stock Level: 671171 693.12 0.000 79.547 0.001 + Cust Report: 3060386 1945.34 0.000 ****** 0.001 + + throughput = 11284.51 bops diff --git a/src/VirtualClient/VirtualClient.Actions/DotNetRuntime/DotNetRuntimeExecutor.cs b/src/VirtualClient/VirtualClient.Actions/DotNetRuntime/DotNetRuntimeExecutor.cs new file mode 100644 index 0000000000..5e41dd75d5 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/DotNetRuntime/DotNetRuntimeExecutor.cs @@ -0,0 +1,378 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.IO.Abstractions; + using System.Text; + using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using VirtualClient; + using VirtualClient.Common; + using VirtualClient.Common.Extensions; + using VirtualClient.Common.Platform; + using VirtualClient.Common.Telemetry; + using VirtualClient.Contracts; + using VirtualClient.Contracts.Metadata; + + /// + /// DotNetRuntime workload executor. + /// + [WindowsCompatible] + public class DotNetRuntimeExecutor : VirtualClientComponent + { + private IFileSystem fileSystem; + private ProcessManager processManager; + private ISystemManagement systemManagement; + + /// + /// The path to the dotNet.props file in DotNetRuntime workload. + /// + private string propsFilePath; + + /// + /// Constructor. + /// + /// Provides required dependencies to the component. + /// Parameters defined in the profile or supplied on the command line. + public DotNetRuntimeExecutor(IServiceCollection dependencies, IDictionary parameters) + : base(dependencies, parameters) + { + this.fileSystem = dependencies.GetService(); + this.processManager = dependencies.GetService(); + this.systemManagement = dependencies.GetService(); + this.SupportingExecutables = new List(); + } + + /// + /// Path to DotNetRuntime Package. + /// + public string PackagePath { get; set; } + + /// + /// The path to the DotNetRuntime executable. + /// + public string ExecutablePath { get; set; } + + /// + /// The number of JVM(Java Virtual Machine) instances of the DotNetRuntime workload to be intialized. + /// + public string NumberOfJvmInstances + { + get + { + this.Parameters.TryGetValue(nameof(DotNetRuntimeExecutor.NumberOfJvmInstances), out IConvertible numberOfJvmInstances); + return numberOfJvmInstances?.ToString(); + } + } + + /// + /// The number of Warehouses to be intialized. + /// + public string NumberOfWarehouses + { + get + { + this.Parameters.TryGetValue(nameof(DotNetRuntimeExecutor.NumberOfWarehouses), out IConvertible numberOfWarehouses); + return numberOfWarehouses?.ToString(); + } + } + + /// + /// The time in seconds that the workload will ramp up/warm up. + /// + public string RampUpSeconds + { + get + { + this.Parameters.TryGetValue(nameof(DotNetRuntimeExecutor.RampUpSeconds), out IConvertible rampUpSeconds); + return rampUpSeconds?.ToString(); + } + } + + /// + /// The time in seconds that the workload will run. + /// + public string MeasurementSeconds + { + get + { + this.Parameters.TryGetValue(nameof(DotNetRuntimeExecutor.MeasurementSeconds), out IConvertible measurementSeconds); + return measurementSeconds?.ToString(); + } + } + + /// + /// The the target throughput when we run the workload. + /// + public string FixedThroughput + { + get + { + this.Parameters.TryGetValue(nameof(DotNetRuntimeExecutor.FixedThroughput), out IConvertible fixedThroughput); + return fixedThroughput?.ToString(); + } + } + + /// + /// Path to the results file after executing dotNetRuntime workload. + /// + public string ResultsFilePath { get; set; } + + /// + /// A set of paths for supporting executables of the main process + /// cleaned up/terminated at the end of each round of processing. + /// + protected IList SupportingExecutables { get; } + + /// + /// Executes DotNet Runtime. + /// + protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + this.ResetResultsFile(telemetryContext); + + try + { + DateTime startTime = DateTime.UtcNow; + await this.ExecuteWorkloadAsync(this.ExecutablePath, telemetryContext, cancellationToken) + .ConfigureAwait(false); + + DateTime endTime = DateTime.UtcNow; + } + finally + { + // Ensure any dotnet.exe instances running are stopped. + this.processManager.SafeKill(this.SupportingExecutables, this.Logger); + } + } + + /// + /// Initializes the environment and dependencies for running the DotNetRuntime workload. + /// + protected override async Task InitializeAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + IPackageManager packageManager = this.Dependencies.GetService(); + DependencyPath workloadPackage = await packageManager.GetPackageAsync(this.PackageName, cancellationToken) + .ConfigureAwait(false); + + if (workloadPackage == null) + { + throw new DependencyException( + $"The expected package '{this.PackageName}' does not exist on the system or is not registered.", + ErrorReason.WorkloadDependencyMissing); + } + + workloadPackage = this.PlatformSpecifics.ToPlatformSpecificPath(workloadPackage, this.Platform, this.CpuArchitecture); + + this.PackagePath = workloadPackage.Path; + this.ExecutablePath = this.PlatformSpecifics.Combine(workloadPackage.Path, "dotnet.bat"); + this.SupportingExecutables.Add(this.PlatformSpecifics.Combine(workloadPackage.Path, "dotnet.exe")); + this.propsFilePath = this.PlatformSpecifics.Combine(workloadPackage.Path, @"dotNet.props"); + + await this.UpdatePropsFile(this.propsFilePath); + + if (!this.fileSystem.File.Exists(this.ExecutablePath)) + { + throw new DependencyException( + $"DotNetRuntime executable not found at path '{this.ExecutablePath}'", + ErrorReason.WorkloadDependencyMissing); + } + + this.ResultsFilePath = this.PlatformSpecifics.Combine(this.PackagePath, @"results\results.txt"); + } + + /// + /// Updates the parameters value in dotnet.props file with the values provided through command line. + /// + private async Task UpdatePropsFile(string propsFilePath) + { + if (this.NumberOfJvmInstances != null) + { + await this.ReplaceInFileAsync( + propsFilePath, @"input.jvm_instances=[0-9]+", $"input.jvm_instances={this.NumberOfJvmInstances}"); + } + + if (this.NumberOfWarehouses != null) + { + await this.ReplaceInFileAsync( + propsFilePath, + @"input.sequence_of_number_of_warehouses=[0-9]+", + $"input.sequence_of_number_of_warehouses={this.NumberOfWarehouses}"); + } + + if (this.RampUpSeconds != null) + { + await this.ReplaceInFileAsync( + propsFilePath, + @"input.ramp_up_seconds=[0-9]+", + $"input.ramp_up_seconds={this.RampUpSeconds}"); + } + + if (this.MeasurementSeconds != null) + { + await this.ReplaceInFileAsync( + propsFilePath, + @"input.measurement_seconds=[0-9]+", + $"input.measurement_seconds={this.MeasurementSeconds}"); + } + + if (this.FixedThroughput != null) + { + await this.ReplaceInFileAsync( + propsFilePath, + @"input.fixed_throughput=[0-9]+", + $"input.fixed_throughput={this.FixedThroughput}"); + } + } + + /// + /// Replaces text in a file. + /// + /// Path of the text file. + /// Text to search for. + /// Text to replace the search text. + private async Task ReplaceInFileAsync(string filePath, string searchText, string replaceText) + { + using (Stream stream = this.fileSystem.FileStream.New(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) + { + byte[] fileContent = new byte[stream.Length]; + await stream.ReadAsync(fileContent, 0, fileContent.Length).ConfigureAwait(false); + + string content = Regex.Replace(Encoding.Default.GetString(fileContent), searchText, replaceText); + + stream.SetLength(0); + await stream.WriteAsync(Encoding.UTF8.GetBytes(content), 0, content.Length); + } + } + + private void ResetResultsFile(EventContext telemetryContext) + { + try + { + if (this.fileSystem.File.Exists(this.ResultsFilePath)) + { + this.fileSystem.File.Delete(this.ResultsFilePath); + } + + string resultsDirectory = Path.GetDirectoryName(this.ResultsFilePath); + if (!this.fileSystem.Directory.Exists(resultsDirectory)) + { + this.fileSystem.Directory.CreateDirectory(resultsDirectory); + } + + this.fileSystem.File.WriteAllText(this.ResultsFilePath, string.Empty); + } + catch (IOException exc) + { + this.Logger.LogErrorMessage(exc, telemetryContext); + } + } + + private Task ExecuteWorkloadAsync(string executablePath, EventContext telemetryContext, CancellationToken cancellationToken) + { + EventContext relatedContext = telemetryContext.Clone() + .AddContext("executable", executablePath); + + return this.Logger.LogMessageAsync($"{nameof(DotNetRuntimeExecutor)}.ExecuteWorkload", relatedContext, async () => + { + DateTime startTime = DateTime.UtcNow; + ISystemManagement systemManagement = this.Dependencies.GetService(); + + using (IProcessProxy process = systemManagement.ProcessManager.CreateElevatedProcess(this.Platform, executablePath)) + { + this.CleanupTasks.Add(() => process.SafeKill()); + await process.StartAndWaitAsync(cancellationToken); + + if (!cancellationToken.IsCancellationRequested) + { + try + { + process.ThrowIfWorkloadFailed(); + + // Allow the dotnet.exe application to finish its work calculating results and writing them to + // the results file. + KeyValuePair results = await this.WaitForResultsAsync(this.ResultsFilePath, TimeSpan.FromMinutes(30), cancellationToken); + + await this.LogProcessDetailsAsync(process, telemetryContext, "DotNetRuntime", logToFile: true, results: results); + this.CaptureMetrics(results.Value, executablePath, startTime, DateTime.UtcNow, telemetryContext, cancellationToken); + } + catch + { + await this.LogProcessDetailsAsync(process, telemetryContext, "DotNetRuntime", logToFile: true); + } + } + } + }); + } + + private void CaptureMetrics(string results, string commandArguments, DateTime startTime, DateTime endTime, EventContext telemetryContext, CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + { + if (!string.IsNullOrWhiteSpace(results)) + { + try + { + this.MetadataContract.AddForScenario( + "DotNetRuntime", + commandArguments, + toolVersion: null); + + this.MetadataContract.Apply(telemetryContext); + + DotNetRuntimeMetricsParser dotNetParser = new DotNetRuntimeMetricsParser(results); + IList metrics = dotNetParser.Parse(); + + this.Logger.LogMetrics( + "DotNetRuntime", + "DotNetRuntime", + startTime, + endTime, + metrics, + metricCategorization: string.Empty, + scenarioArguments: commandArguments, + this.Tags, + telemetryContext); + } + catch (SchemaException exc) + { + throw new WorkloadException($"Failed to parse workload results file.", exc, ErrorReason.WorkloadFailed); + } + } + else + { + throw new WorkloadException( + $"Missing results. Workload results were not emitted by the workload.", + ErrorReason.WorkloadFailed); + } + } + } + + private async Task> WaitForResultsAsync(string resultsFilePath, TimeSpan timeout, CancellationToken cancellationToken) + { + KeyValuePair results = default; + DateTime waitTimeout = DateTime.UtcNow.Add(timeout); + + while (!cancellationToken.IsCancellationRequested && DateTime.UtcNow < waitTimeout) + { + string content = await this.fileSystem.File.ReadAllTextAsync(resultsFilePath, cancellationToken); + + if (!string.IsNullOrWhiteSpace(content)) + { + results = new KeyValuePair(resultsFilePath, content); + break; + } + + await Task.Delay(500); + } + + return results; + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/DotNetRuntime/DotNetRuntimeMetricsParser.cs b/src/VirtualClient/VirtualClient.Actions/DotNetRuntime/DotNetRuntimeMetricsParser.cs new file mode 100644 index 0000000000..18277dba94 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/DotNetRuntime/DotNetRuntimeMetricsParser.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Text.RegularExpressions; + using VirtualClient; + using VirtualClient.Contracts; + using DataTableExtensions = VirtualClient.Contracts.DataTableExtensions; + + /// + /// Parser for DotNetRuntime output document. + /// + public class DotNetRuntimeMetricsParser : MetricsParser + { + /// + /// Identifies string starting with digits and ending with digits. + /// + private static readonly Regex ValueUnitSplitRegex = new Regex(@"(?<=\d)( )(?=\w)", RegexOptions.ExplicitCapture); + + /// + /// Sectionize by one or more empty lines. + /// + private static readonly Regex DotNetRuntimeSectionDelimiter = new Regex(@"(\n)(\s)*(\n)", RegexOptions.ExplicitCapture); + + /// + /// Separate the column values by 2 or more spaces. + /// + private static readonly Regex DotNetRuntimeDataTableDelimiter = new Regex(@"(\s){2,}", RegexOptions.ExplicitCapture); + + /// + /// Regex for removing "==========" lines. + /// + private static readonly Regex EqualLineRegex = new Regex(@"(=){2,}(\s)*", RegexOptions.ExplicitCapture); + + /// + /// constructor for . + /// + /// Raw text to parse. + public DotNetRuntimeMetricsParser(string rawText) + : base(rawText) + { + } + + /// + /// Throughput result for DotNet Runtime. + /// + public DataTable ThroughputResult { get; set; } + + /// + public override IList Parse() + { + this.Preprocess(); + this.Sections = TextParsingExtensions.Sectionize(this.PreprocessedText, DotNetRuntimeSectionDelimiter); + this.ThrowIfInvalidOutputFormat(); + this.CalculateThroughputResult(); + + List metrics = new List(); + metrics.AddRange(this.ThroughputResult.GetMetrics(nameIndex: 0, valueIndex: 2, unitIndex: 3, metricRelativity: MetricRelativity.HigherIsBetter)); + return metrics; + } + + /// + protected override void Preprocess() + { + this.PreprocessedText = TextParsingExtensions.RemoveRows(this.RawText, DotNetRuntimeMetricsParser.EqualLineRegex); + this.PreprocessedText = this.PreprocessedText.Replace("TOTALS FOR:", $"DotNetSummary{Environment.NewLine}"); + this.PreprocessedText = Regex.Replace(this.PreprocessedText, @"throughput =", $"{Environment.NewLine}Throughput{Environment.NewLine}throughput"); + } + + private void CalculateThroughputResult() + { + string sectionName = "Throughput"; + IList columnNames = new List { "Name", "Measurement" }; + this.ThroughputResult = DataTableExtensions.ConvertToDataTable( + this.Sections[sectionName], DotNetRuntimeMetricsParser.DotNetRuntimeDataTableDelimiter, sectionName, columnNames); + + IList splitColumnNames = new List { "Value", "Unit" }; + this.ThroughputResult.SplitDataColumn(columnIndex: 1, DotNetRuntimeMetricsParser.ValueUnitSplitRegex, splitColumnNames); + } + + /// + private void ThrowIfInvalidOutputFormat() + { + if (this.Sections.Count <= 0 || !this.Sections.ContainsKey("Throughput")) + { + throw new SchemaException("The DotNetRuntime output file has incorrect format for parsing"); + } + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-DOTNETRUNTIME.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-DOTNETRUNTIME.json new file mode 100644 index 0000000000..e127fc1317 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-DOTNETRUNTIME.json @@ -0,0 +1,36 @@ +{ + "Description": "DotNetRuntime CPU Performance workload", + "Parameters": { + "NumberOfJvmInstances": "1", + "NumberOfWarehouses": "8", + "RampUpSeconds": "60", + "MeasurementSeconds": "3600", + "FixedThroughput": "16000" + }, + "Actions": [ + { + "Type": "DotNetRuntimeExecutor", + "Parameters": { + "Scenario": "CpuOperationThroughput", + "NumberOfJvmInstances": "$.Parameters.NumberOfJvmInstances", + "PackageName": "dotnetruntime", + "NumberOfWarehouses": "$.Parameters.NumberOfWarehouses", + "RampUpSeconds": "$.Parameters.RampUpSeconds", + "MeasurementSeconds": "$.Parameters.MeasurementSeconds", + "FixedThroughput": "$.Parameters.FixedThroughput" + } + } + ], + "Dependencies": [ + { + "Type": "DependencyPackageInstallation", + "Parameters": { + "Scenario": "InstallDotNetRuntimePackage", + "BlobContainer": "packages", + "BlobName": "dotnetruntime.4.6.27817.3-1.zip", + "PackageName": "dotnetruntime", + "Extract": true + } + } + ] +} \ No newline at end of file diff --git a/website/docs/workloads/dotnetruntime/dotnetruntime-profiles.md b/website/docs/workloads/dotnetruntime/dotnetruntime-profiles.md new file mode 100644 index 0000000000..463a08bba0 --- /dev/null +++ b/website/docs/workloads/dotnetruntime/dotnetruntime-profiles.md @@ -0,0 +1,56 @@ +# DotNetRuntime Workload Profiles +The following profiles run customer-representative or benchmarking scenarios using the .NET Runtime workload. + +* [Getting Started](https://microsoft.github.io/VirtualClient/) +* [Workload Details](./dotnetruntime.md) + +## PERF-CPU-DOTNETRUNTIME.json +Runs a CPU-intensive workload using the DotNetRuntime toolset to test the performance of the CPU in processing transactions to the database(warehouse). +This profile is designed by the Intel team as part of Cloud R1 Workload to identify general/broad regressions when compared against a baseline. + +* [Workload Profile](https://msazure.visualstudio.com/One/_git/CRC-AIR-Workloads?path=/src/VirtualClient/CRC.VirtualClient.Packaging/profiles/PERF-CPU-DOTNETRUNTIME.json) + +* **Supported Platform/Architectures** + * win-x64 + * win-arm64 + +* **Supported Operating Systems** + * Windows + +* **Dependencies** + The dependencies defined in the 'Dependencies' section of the profile itself are required in order to run the workload operations effectively. + * Internet connection. + + Additional information on components that exist within the 'Dependencies' section of the profile can be found in the following locations: + * [Installing Dependencies](https://microsoft.github.io/VirtualClient/docs/category/dependencies/) + +* **Profile Parameters** + The following parameters can be optionally supplied on the command line to modify the behaviors of the workload. + + | Parameter | Purpose | Default value | + |---------------------------|---------------------------------------------------------------------------------|---------------| + | NumberOfJvmInstances | Optional. This specifies the number of JVM(Java Virtual Machine) application instances to run. | 1 + | NumberOfWarehouses | Optional. This specifies the number of warehouses that we can have. | 8 + | RampUpSeconds | Optional. This specifies the time in seconds that the workload will ramp up/warm up. | 60 + | MeasurementSeconds | Optional. This specifies the time in seconds that the workload will run. | 3600 + | FixedThroughput | Optional. This specifies the target throughput. The workload will try to target the specified throughput. | 16000 | + +* **Profile Runtimes** + The following timings represent the length of time required to run a single round of profile actions. These timings can be used to determine + minimum required runtimes for the Virtual Client in order to get results. These are estimates based on the number of system cores. + This particular workload runtime is affected directly by the value of the 'MeasurementSeconds' parameter of the profile. The larger the value, + the longer the runtime. + + * Expected Runtime = 1 hour + +* **Usage Examples** + The following section provides a few basic examples of how to use the workload profile. + + ``` bash + # Execute the workload profile + VirtualClient.exe --profile=PERF-CPU-DOTNETRUNTIME.json --system=Juno --timeout=1440 --packageStore="{BlobConnectionString|SAS Uri}" + + # Increase the scale factors. + VirtualClient.exe --profile=PERF-CPU-DOTNETRUNTIME.json --system=Juno --timeout=1440 --packageStore="{BlobConnectionString|SAS Uri}" --parameters=NumberOfJvmInstances=2 + VirtualClient.exe --profile=PERF-CPU-DOTNETRUNTIME.json --system=Juno --timeout=1440 --packageStore="{BlobConnectionString|SAS Uri}" --parameters=NumberOfJvmInstances=2,,,NumberOfWarehouses=16 + ``` \ No newline at end of file diff --git a/website/docs/workloads/dotnetruntime/dotnetruntime-supplemental.md b/website/docs/workloads/dotnetruntime/dotnetruntime-supplemental.md new file mode 100644 index 0000000000..bc3dad5e70 --- /dev/null +++ b/website/docs/workloads/dotnetruntime/dotnetruntime-supplemental.md @@ -0,0 +1,72 @@ +# DotNetRuntime Workload Supplemental +The following information is additional/supplemental to the documentation available for the DotNetRuntime workload. This information is intended for +use by teams internal to Microsoft and their affiliates. + +## System Recommendations +The following sections provide recommendations to consider when running Virtual Client profiles (workloads, monitors and tests) on +a system. + +### PERF-CPU-DOTNETRUNTIME.json +The following configurations are general recommendations for use when running this profile on cloud hardware systems and virtual machines. + +* **Recommended Configurations (Azure Cloud)** + Note that the term "cores" as used below in describing VM specifications should be inferred as synonymous with the term virtual CPU (vCPU). The configurations + below cover those used by the CRC team for running this workload as part of the Virtual Client platform. These come from recommendations and empirical + evidence from running on Azure cloud systems and are designed to mimic "customer-representative" scenarios or to utilize/stress the physical nodes/systems. + These configurations have generally proven to be well-suited for net impact analysis on systems where a change is being applied to the physical hardware + (e.g. a firmware update). + + * Operating System (unless otherwise specified below) + * Windows Scenarios + * Publisher: MicrosoftWindowsServer + * Offer: WindowsServer + * Sku: 2019-Datacenter + * Version: latest +

+ * AMD Gen6 (Naples) Hardware + * Virtual Machines (per node) + * Firmware/Hardware Validations = 10 x 2-core -> Standard_L2_v2 + * Firmware/Hardware Validations (Intel R1) = TBD + * Test/QoS = 1 x 2-core -> Standard_L2_v2 +

+ * AMD Gen7 (Rome) Hardware + * Virtual Machines (per node) + * Firmware/Hardware Validations = 10 x 2-core -> Standard_D2a_v4, Standard_E2a_v4 + * Firmware/Hardware Validations (Intel R1) = TBD + * Test/QoS = 1 x 2-core -> Standard_D2a_v4, Standard_E2a_v4 +

+ * AMD Gen8 (Milan) Hardware + * Virtual Machines (per node) + * Firmware/Hardware Validations = 10 x 2-core -> Standard_D2a_v4/v5, Standard_E2a_v4/v5 + * Firmware/Hardware Validations (Intel R1) = TBD + * Test/QoS = 1 x 2-core -> Standard_D2a_v4/v5, Standard_E2a_v4/v5 +

+ * Intel Gen5 (Broadwell) Hardware + * Virtual Machines (per node) + * Firmware/Hardware Validations = 10 x 2-core -> Standard_D2_v3, Standard_E2_v3, Standard_F2_v2 + * Firmware/Hardware Validations (Intel R1) = TBD + * Test/QoS = 1 x 2-core -> Standard_D2_v3, Standard_E2_v3, Standard_F2 +

+ * Intel Gen6 (Coffee Lake) Hardware + * Virtual Machines (per node) + * Firmware/Hardware Validations = 10 x 2-core -> Standard_D2_v3, Standard_E2_v3, Standard_F2_v2 + * Firmware/Hardware Validations (Intel R1) = TBD + * Test/QoS = 1 x 2-core -> Standard_D2_v3, Standard_E2_v3, Standard_F2_v2 +

+ * Intel Gen6 (Skylake) Hardware + * Virtual Machines (per node) + * Firmware/Hardware Validations = 10 x 2-core -> Standard_D2_v3, Standard_E2_v3, Standard_F2_v2 + * Firmware/Hardware Validations (Intel R1) = TBD + * Test/QoS = 1 x 2-core -> Standard_D2_v3, Standard_E2_v3, Standard_F2_v2 +

+ * Intel Gen7 (Cascade Lake) Hardware + * Virtual Machines (per node) + * Firmware/Hardware Validations = 10 x 2-core -> Standard_D2_v3/v4, Standard_E2_v3/v4, Standard_F2_v2 + * Firmware/Hardware Validations (Intel R1) = TBD + * Test/QoS = 1 x 2-core -> Standard_D2_v3/v4, Standard_E2_v3/v4, Standard_F2_v2 +

+ * Intel Gen8 (Icelake) Hardware + * Virtual Machines (per node) + * Firmware/Hardware Validations = 10 x 2-core -> Standard_D2_v4/v5, Standard_E2_v4/v5, Standard_F2_v2 + * Firmware/Hardware Validations (Intel R1) = TBD + * Test/QoS = 1 x 2-core -> Standard_D2_v4/v5, Standard_E2_v4/v5, Standard_F2_v2 \ No newline at end of file diff --git a/website/docs/workloads/dotnetruntime/dotnetruntime-workload-overview.docx b/website/docs/workloads/dotnetruntime/dotnetruntime-workload-overview.docx new file mode 100644 index 0000000000000000000000000000000000000000..86c563c0dea2e0dad98e1aaae4fcfce6c09878c4 GIT binary patch literal 17315 zcmeIagMVeq(m%Xo+qP|MV%xTzOl;fcOl(Z-OzdQ0CllM&n{%IgpL6cH_x%fg&)T1= zz1Hgf)>^&0s!`Rw6{JBxQ32opNB{sp1V}WY%QXc80CKGFy}PXq;dfAA%6tIuXa4^?{vZAY8j{E4`WO*KA0%Fdcj=_o>IEU$&H{&!sTKKq z8Y!wm^y^D%y3KzzAblx9YFHPMGCSvUtBi&yXk=SPHu|-ZQmKEd0WRlRv|edKkd*eC zYYG*j{L>wRY*o;xVEb;~pdKooS*cy6p3BI6i+6-eBLSMAN3rO72D)&Ob4O@co(aTv z^<>@g2rsY?*BXHxmsf}k9sI`un~zf_j%WMCSAjnIFO%t{JH~_0Gb9_>wF$v8+i0GpybVR0jq@MMqCU&Nl?&u z4ja%AFwOfyaj>+4E39heJ4{JX9oH|`2Shu|HG#E zm!elD%1VPUA%Ko85q|uGnhz&{zwU`Z76vh^Owb1)BAIZS>YpB55b}hL|{t znnryn`V-IS7oWCN2*T;L>`4LH$k37@;oBFyopM@x#&LaSl)mRjIW^+#Qkdoro*$Pp+-YW0awpVX(&pNqGBNU99lVcw+{^#n#1vk zRsHj z0RSW(0Kn%Lz|G#tgu%q#*wyy4NBg5IJLR*s|2y5{93|hb1ndhk10~MuCdS=P zkvVdHC_xj0-43+5WS94qC-{x+pm|OIYsCsebU<*2#BdH4l|g&Ej_yS{eOC9|8$IkW z9ZJMh!C2_V9ColM{llw;@ABiDr_+XIyvVQN0-(4hQ{#ce!A-A^H+?^CJ@++PVRT7g z!tc9ASnX*sA<0*w6)7z+=)VfFo~yT@@KMXW8!P(AP{Qfgf;w z9j21U;AVTdVY13klgWtrtBvU@5G}7`hrU}7eyvzNwgF+z1wg7<9Iob%iw8Mrh0miC zo=ExwD(EEcX9>?VvTs-=2H6Wg&ofrzx#fcz$mF)0{BP1l9UKQmCCG3iJ!lGllgCT} zImi?V+=1v<1B&-i4N3{td4WjMRlh*WUd$k_RK0icYDM|LQ zw}=i8ZV`fDZ(fLwSAcMZ5ci^m6_z^810*4pTDq>1B$SDⅇ2+0GQeU+(iL}LKsXe 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 Date: Sat, 30 May 2026 12:03:18 -0700 Subject: [PATCH 2/4] Very minor documentation change. --- .../workloads/dotnetruntime/dotnetruntime.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/website/docs/workloads/dotnetruntime/dotnetruntime.md b/website/docs/workloads/dotnetruntime/dotnetruntime.md index 3cbb0d8f63..8dc6feaf4b 100644 --- a/website/docs/workloads/dotnetruntime/dotnetruntime.md +++ b/website/docs/workloads/dotnetruntime/dotnetruntime.md @@ -1,4 +1,4 @@ -# DotNetRuntime Workload +# DotNetRuntime The DotNetRuntime workload is a .NET program which mimics a 3-tier system with emphasis on the middle tier.The first tier is a set of random input selections. This workload is a compute intensive workload and drives up the CPU utilization of the system that it is running on. It is representative of a middle tier system and is simplified for easy benchmarking. @@ -9,7 +9,14 @@ running on. It is representative of a middle tier system and is simplified for e DotNetRuntime workload is designed to be a very simple benchmarking tool. It produces a measurement of **throughput** for each run of the workload on the system. -* Throughput in bops(billions of operations per second) +* Throughput in bops (billions of operations per second) + +## Workload Metrics +The following metrics are produced by the DotNetRuntime workload itself. + +| Metric Name | Example Value (min) | Example Value (max) | Example Value (avg) | Unit | +|-------------|---------------------|------| +| throughput | 15937.97 | 16141.61 | 16047.287142857143 | bops | ## System Metrics Different metrics are captured from the system depending upon which monitor profiles are used. If a monitor profile is not @@ -17,11 +24,4 @@ defined, the default MONITORS-DEFAULT.json profile is used. See the following do that are available. * [Monitor Profiles](https://github.com/microsoft/VirtualClient/blob/main/website/docs/monitors/monitor-profiles.md) -* [Monitor Profiles (internal only)](../../monitors/monitor-profiles.md) - -## Workload Metrics -The following metrics are produced by the DotNetRuntime workload itself. - -| Metric Name | Example Value (min) | Example Value (max) | Example Value (avg) | Unit | -|-------------|---------------------|------| -| throughput | 15937.97 | 16141.61 | 16047.287142857143 | bops | \ No newline at end of file +* [Monitor Profiles (internal only)](../../monitors/monitor-profiles.md) \ No newline at end of file From fdda662abeb00eed3fc7a333be0cb22c09b67fdd Mon Sep 17 00:00:00 2001 From: saibulusu Date: Sat, 30 May 2026 19:45:09 -0700 Subject: [PATCH 3/4] adding one example file --- .../Examples/IncorrectDotNetRuntimeResultsExample.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/IncorrectDotNetRuntimeResultsExample.txt diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/IncorrectDotNetRuntimeResultsExample.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/IncorrectDotNetRuntimeResultsExample.txt new file mode 100644 index 0000000000..05fa4620f9 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/IncorrectDotNetRuntimeResultsExample.txt @@ -0,0 +1 @@ +This file is IncorrectDotNetRuntime example. \ No newline at end of file From 8806766ae75e244f0aa5ee7750266b8ba1f6ca27 Mon Sep 17 00:00:00 2001 From: saibulusu Date: Mon, 1 Jun 2026 10:15:28 -0700 Subject: [PATCH 4/4] upversion --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bea438e9ad..4772543317 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.3.1 +3.3.2