Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace VirtualClient.Actions
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
Expand Down Expand Up @@ -45,6 +46,59 @@ public void SpecCpuWorkloadProfileParametersAreInlinedCorrectly(string profile)
}
}

[Test]
[TestCase("PERF-SPECCPU-INTRATE.json", PlatformID.Unix, "intrate")]
[TestCase("PERF-SPECCPU-INTRATE.json", PlatformID.Win32NT, "505 525 541 548 557")]
[TestCase("PERF-SPECCPU-FPRATE.json", PlatformID.Unix, "fprate")]
[TestCase("PERF-SPECCPU-FPRATE.json", PlatformID.Win32NT, "503 508 510 519 544 549 554")]
public async Task SpecCpuRateProfilesRunTheFullSuiteWithNumaProcessorAffinityDisabledByDefault(
string profileName,
PlatformID platform,
string expectedBenchmarks)
{
this.mockFixture.Setup(platform);

ExecutionProfile profile = await ExecutionProfile.ReadProfileAsync(Path.Combine(MockFixture.TestResourcesDirectory, "profiles", profileName));
profile.Parameters["CopyCount"] = 4;

Assert.AreEqual(1, profile.Actions.Count, "A SPEC CPU rate suite must run as one action, not separate benchmarks.");
ExecutionProfileElement action = profile.Actions.Single();
Assert.AreEqual("SpecCpuExecutor", action.Type);
Assert.AreEqual("ExecuteSPECBenchmark", action.Parameters["Scenario"]);
Assert.AreEqual("$.Parameters.Benchmarks", action.Parameters["Benchmarks"], "The suite action must use the platform-specific benchmark selection.");
Assert.AreEqual(string.Empty, profile.Parameters["NumaProcessorAffinity"]);
Assert.AreEqual("$.Parameters.NumaProcessorAffinity", action.Parameters["NumaProcessorAffinity"]);
Assert.IsFalse(profile.Actions.Any(item => item.Parameters["Scenario"].ToString().StartsWith("benchmark_", StringComparison.OrdinalIgnoreCase)));

profile.Inline();
await ProfileExpressionEvaluator.Instance.EvaluateAsync(this.mockFixture.Dependencies, action.Parameters);

Assert.AreEqual(expectedBenchmarks, action.Parameters["Benchmarks"]);
Assert.AreEqual(string.Empty, action.Parameters["NumaProcessorAffinity"]);
Assert.IsFalse(action.Parameters.ContainsKey("EnableNumaProcessorAffinitySymmetric"));
Assert.AreEqual("4", action.Parameters["CopyCount"].ToString());
}

[Test]
[TestCase("PERF-SPECCPU-INTRATE.json", 2, "symmetric")]
[TestCase("PERF-SPECCPU-INTRATE.json", 4, "SyMmEtRiC")]
[TestCase("PERF-SPECCPU-FPRATE.json", 2, "symmetric")]
[TestCase("PERF-SPECCPU-FPRATE.json", 4, "0,1,48,49")]
public async Task SpecCpuRateProfilesForwardNumaProcessorAffinity(string profileName, int copyCount, string affinity)
{
this.mockFixture.Setup(PlatformID.Unix);
ExecutionProfile profile = await ExecutionProfile.ReadProfileAsync(Path.Combine(MockFixture.TestResourcesDirectory, "profiles", profileName));
profile.Parameters["CopyCount"] = copyCount;
profile.Parameters["NumaProcessorAffinity"] = affinity;

profile.Inline();

ExecutionProfileElement action = profile.Actions.Single();
Assert.AreEqual(affinity, action.Parameters["NumaProcessorAffinity"]);
Assert.IsFalse(action.Parameters.ContainsKey("EnableNumaProcessorAffinitySymmetric"));
Assert.AreEqual(copyCount.ToString(), action.Parameters["CopyCount"].ToString());
}

[Test]
[TestCase("PERF-SPECCPU2026-FPRATE.json", Architecture.X64)]
[TestCase("PERF-SPECCPU2026-FPRATE.json", Architecture.Arm64)]
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// Licensed under the MIT License.

namespace VirtualClient.Actions
Expand Down
465 changes: 438 additions & 27 deletions src/VirtualClient/VirtualClient.Actions/SPECcpu/SpecCpuExecutor.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,33 @@ public void MetricsCsvFileLoggerUsesTheExpectedColumnNamesForTheCsvFiles()
CollectionAssert.AreEqual(expectedColumnHeaders, MetricsCsvFileLogger.CsvFields.Select(field => field.ColumnName));
}

[Test]
public void MetricsCsvFileLoggerDoesNotExportNumaCpuListsFromMetricMetadata()
{
EventContext context = new EventContext(Guid.NewGuid(), new Dictionary<string, object>
{
["toolName"] = "SPECcpu",
["scenarioName"] = "ExecuteSPECBenchmark",
["metricName"] = "node-cpus-0",
["metricValue"] = 4,
["metricUnit"] = "count",
["metadata_metrics"] = new Dictionary<string, IConvertible>
{
["cpuIds"] = "0,1,48,49",
["selectedCpuIds"] = "0,48",
["processorAffinity"] = "0,1,48,49"
}
});

string csv = MetricsCsvFileLogger.CreateMessage(context);

StringAssert.Contains("\"SPECcpu\"", csv);
StringAssert.Contains("\"node-cpus-0\",\"4\",\"count\"", csv);
StringAssert.DoesNotContain("0,1,48,49", csv);
StringAssert.DoesNotContain("0,48", csv);
StringAssert.DoesNotContain("cpuIds", csv);
}

[Test]
public void MetricsCsvFileLoggerWritesTheExpectedCsvToFiles_1()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ public void CreateProcessWithAffinityCreatesExpectedBashCommandOnLinux()
}
}

[Test]
public void CreateProcessAffinityOverloadCreatesExpectedBashCommandOnLinux()
{
this.SetupDefaults(PlatformID.Unix);

ProcessAffinityConfiguration config = ProcessAffinityConfiguration.Create(PlatformID.Unix, new[] { 0, 1, 2 });

using (IProcessProxy process = this.mockFixture.ProcessManager.CreateProcess(
"bash -c",
"myworkload --option1=value",
"/home/user/workdir",
config))
{
Assert.AreEqual("bash -c \"numactl -C 0-2 myworkload --option1=value\"", process.StartInfo.FileName);
Assert.AreEqual("/home/user/workdir", process.StartInfo.WorkingDirectory);
}
}

[Test]
public void CreateProcessWithAffinityThrowsOnNullConfiguration()
{
Expand Down Expand Up @@ -109,6 +127,26 @@ public void CreateElevatedProcessWithAffinityCreatesExpectedCommandOnLinux()
}
}

[Test]
public void CreateProcessElevatedAffinityOverloadCreatesExpectedCommandOnLinux()
{
this.SetupDefaults(PlatformID.Unix);

ProcessAffinityConfiguration config = ProcessAffinityConfiguration.Create(PlatformID.Unix, new[] { 0, 1, 2 });

using (IProcessProxy process = this.mockFixture.ProcessManager.CreateProcess(
PlatformID.Unix,
"bash -c",
"myworkload --option1=value",
"/home/user/workdir",
config))
{
Assert.AreEqual("sudo", process.StartInfo.FileName);
Assert.AreEqual("bash -c \"numactl -C 0-2 myworkload --option1=value\"", process.StartInfo.Arguments);
Assert.AreEqual("/home/user/workdir", process.StartInfo.WorkingDirectory);
}
}

[Test]
public void CreateElevatedProcessWithAffinityHandlesComplexArguments()
{
Expand Down
30 changes: 30 additions & 0 deletions src/VirtualClient/VirtualClient.Core/ProcessExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,36 @@ public static IProcessProxy CreateElevatedProcessWithAffinity(this ProcessManage
}
}

/// <summary>
/// Creates a Linux process with CPU affinity binding to specific cores.
/// </summary>
/// <param name="processManager">The process manager used to create the process.</param>
/// <param name="command">The command to run.</param>
/// <param name="arguments">The command line arguments to supply to the command.</param>
/// <param name="workingDir">The working directory for the command.</param>
/// <param name="affinityConfig">The CPU affinity configuration specifying which cores to bind to.</param>
/// <returns>A process proxy with CPU affinity applied via numactl.</returns>
public static IProcessProxy CreateProcess(this ProcessManager processManager, string command, string arguments, string workingDir, ProcessAffinityConfiguration affinityConfig)
{
return processManager.CreateProcessWithAffinity(command, arguments, workingDir, affinityConfig);
}

/// <summary>
/// Creates a Linux process with CPU affinity binding and elevated privileges when needed.
/// </summary>
/// <param name="processManager">The process manager used to create the process.</param>
/// <param name="platform">The OS platform.</param>
/// <param name="command">The command to run.</param>
/// <param name="arguments">The command line arguments to supply to the command.</param>
/// <param name="workingDir">The working directory for the command.</param>
/// <param name="affinityConfig">The CPU affinity configuration specifying which cores to bind to.</param>
/// <param name="username">The username to use for running the command (Linux only).</param>
/// <returns>A process proxy with CPU affinity and elevated privileges applied.</returns>
public static IProcessProxy CreateProcess(this ProcessManager processManager, PlatformID platform, string command, string arguments, string workingDir, ProcessAffinityConfiguration affinityConfig, string username = null)
{
return processManager.CreateElevatedProcessWithAffinity(platform, command, arguments, workingDir, affinityConfig, username);
}

/// <summary>
/// Applies Windows CPU affinity to a running process.
/// This should be called after the process has started.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"RecommendedMinimumExecutionTime": "(4-cores)=02:00:00,(16-cores)=05:00:00,(64-cores)=10:00:00",
"SupportedPlatforms": "linux-x64,linux-arm64,win-x64,win-arm64",
"SupportedOperatingSystems": "AzureLinux,CentOS,Debian,OpenSuse,RedHat,Ubuntu,Windows",
"Notes": "Using a private package 'speccpu.2017.1.1.9-danielbowers.zip' to enable support for speccpu on Ubuntu 24. SPEC CPU 2017 was not initially designed for win-arm64.",
"Notes": "Private package speccpu.2017.1.1.9-danielbowers.zip supports Ubuntu 24; SPEC CPU 2017 was not designed for win-arm64. NumaProcessorAffinity (Linux rate only): 'symmetric' balances copies across NUMA nodes, one CPU each; explicit CPU IDs pin copies manually; empty (default) does not pin.",
"Limitations": "The following benchmarks don't work on Windows: 507, 511, 521, 526, 527, 538"
},
"ParameterAliases": {
Expand All @@ -25,6 +25,7 @@
"RunPeak": false,
"ThreadCount": "{LogicalCoreCount}",
"CopyCount": "{LogicalCoreCount}",
"NumaProcessorAffinity": "",
"BaseOptimizingFlags": "-O2 -march={calculate((\"{Architecture}\" == \"x64\") ? \"x86-64-v3\" : \"armv8.2-a\")} -flto -frecord-gcc-switches",
"PeakOptimizingFlags": "-O3 -march={calculate((\"{Architecture}\" == \"x64\") ? \"x86-64-v3\" : \"armv8.2-a\")} -flto -frecord-gcc-switches"
},
Expand All @@ -40,6 +41,7 @@
"RunPeak": "$.Parameters.RunPeak",
"ThreadCount": "$.Parameters.ThreadCount",
"CopyCount": "$.Parameters.CopyCount",
"NumaProcessorAffinity": "$.Parameters.NumaProcessorAffinity",
"BaseOptimizingFlags": "$.Parameters.BaseOptimizingFlags",
"PeakOptimizingFlags": "$.Parameters.PeakOptimizingFlags"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"RecommendedMinimumExecutionTime": "(8-cores)=04:00:00,(16-cores)=08:00:00,(64-cores)=10:00:00",
"SupportedPlatforms": "linux-x64,linux-arm64,win-x64",
"SupportedOperatingSystems": "AzureLinux,CentOS,Debian,OpenSuse,RedHat,Ubuntu,Windows",
"Notes": "Using a private package 'speccpu.2017.1.1.9-danielbowers.zip' to enable support for speccpu on Ubuntu 24. SPEC CPU 2017 was not initially designed for win-arm64.",
"Notes": "Private package speccpu.2017.1.1.9-danielbowers.zip supports Ubuntu 24; SPEC CPU 2017 was not designed for win-arm64. NumaProcessorAffinity (Linux rate only): 'symmetric' balances copies across NUMA nodes, one CPU each; explicit CPU IDs pin copies manually; empty (default) does not pin.",
"Limitations": "The following benchmarks don't work on Windows: 500, 502, 520, 523, 531"
},
"ParameterAliases": {
Expand All @@ -25,6 +25,7 @@
"RunPeak": false,
"ThreadCount": "{LogicalCoreCount}",
"CopyCount": "{LogicalCoreCount}",
"NumaProcessorAffinity": "",
"BaseOptimizingFlags": "-O2 -march={calculate((\"{Architecture}\" == \"x64\") ? \"x86-64-v3\" : \"armv8.2-a\")} -flto -frecord-gcc-switches",
"PeakOptimizingFlags": "-O3 -march={calculate((\"{Architecture}\" == \"x64\") ? \"x86-64-v3\" : \"armv8.2-a\")} -flto -frecord-gcc-switches"
},
Expand All @@ -40,6 +41,7 @@
"RunPeak": "$.Parameters.RunPeak",
"ThreadCount": "$.Parameters.ThreadCount",
"CopyCount": "$.Parameters.CopyCount",
"NumaProcessorAffinity": "$.Parameters.NumaProcessorAffinity",
"BaseOptimizingFlags": "$.Parameters.BaseOptimizingFlags",
"PeakOptimizingFlags": "$.Parameters.PeakOptimizingFlags"
}
Expand Down
14 changes: 12 additions & 2 deletions website/docs/workloads/speccpu/speccpu-profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ for evaluating the performance of the CPU for processing calculations.
| RunPeak | Optional. True to run the workload 'Peak' scenario, False to run the workload 'Base' scenario. | false (Base) |
| ThreadCount | Optional. Determines the number of threads to use for running the benchmark. | # logical processors |
| CopyCount | Optional. Determines the number of copies of the benchmark to run concurrently. | # logical processors |
| NumaProcessorAffinity | Optional. Linux SPECrate only. Set to `symmetric` to balance copies across NUMA nodes, one CPU per copy using `numactl`, or provide CPU IDs to pin copies manually. Empty leaves copies unpinned. | empty |
| BaseOptimizationFlags | Optional. Optimization flags to pass to the GCC compiler when running the 'Base' scenario | <ul><li>X64 systems: -g -O2 -march=x86-64-v3 -flto -frecord-gcc-switches</li><li>ARM64 systems: -g -O2 -march=armv8.2-a -flto -frecord-gcc-switches</li></ul> |
| PeakOptimizationFlags | Optional. Optimization flags to pass to the GCC compiler when running the 'Peak' scenario | <ul><li>X64 systems: -g -O3 -march=x86-64-v3 -flto -frecord-gcc-switches</li><li>ARM64 systems: -g -O3 -march=armv8.2-a -flto -frecord-gcc-switches</li></ul> |

Expand All @@ -64,6 +65,7 @@ for evaluating the performance of the CPU for processing calculations.

* **Notes**
On Windows, only the following benchmarks are supported: 503.bwaves_r, 508.namd_r, 510.parest_r, 519.lbm_r, 544.nab_r, 549.fotonik3d_r, 554.roms_r.
The Linux `NumaProcessorAffinity` examples in the INTRATE section below also apply to this profile.

``` bash
# Execute the workload profile
Expand Down Expand Up @@ -155,6 +157,7 @@ for evaluating the performance of the CPU for processing calculations.
| RunPeak | Optional. True to run the workload 'Peak' scenario, False to run the workload 'Base' scenario. | false (Base) |
| ThreadCount | Optional. Determines the number of threads to use for running the benchmark. | # logical processors |
| CopyCount | Optional. Determines the number of copies of the benchmark to run concurrently. | # logical processors |
| NumaProcessorAffinity | Optional. Linux SPECrate only. Set to `symmetric` to balance copies across NUMA nodes, one CPU per copy using `numactl`, or provide CPU IDs to pin copies manually. Empty leaves copies unpinned. | empty |
| BaseOptimizationFlags | Optional. Optimization flags to pass to the GCC compiler when running the 'Base' scenario | <ul><li>X64 systems: -g -O2 -march=x86-64-v3 -flto -frecord-gcc-switches</li><li>ARM64 systems: -g -O2 -march=armv8.2-a -flto -frecord-gcc-switches</li></ul> |
| PeakOptimizationFlags | Optional. Optimization flags to pass to the GCC compiler when running the 'Peak' scenario | <ul><li>X64 systems: -g -O3 -march=x86-64-v3 -flto -frecord-gcc-switches</li><li>ARM64 systems: -g -O3 -march=armv8.2-a -flto -frecord-gcc-switches</li></ul> |

Expand All @@ -174,10 +177,17 @@ for evaluating the performance of the CPU for processing calculations.

* **Notes**
On Windows, only the following benchmarks are supported: 505.mcf_r, 525.x264_r, 541.leela_r, 548.exchange2_r, 557.xz_r.
On Linux, `NumaProcessorAffinity=symmetric` selects CPUs from `numactl --hardware`, giving each node as even a share of `CopyCount` as its available CPUs allow. If `CopyCount` is not divisible by the number of nodes, lower-numbered nodes receive the extra copies. Pinned copies use `numactl --localalloc` for local memory allocation. These examples assume 96 CPUs on two NUMA nodes: CPUs 0-47 on node 0 and 48-95 on node 1. Check the actual topology with `numactl --hardware`.

``` bash
# Execute the workload profile
./VirtualClient --profile=PERF-SPECCPU-INTRATE.json --iterations=1 --logger=csv
# Two INTRATE copies: CPU 0 on node 0 and CPU 48 on node 1.
./VirtualClient --profile=PERF-SPECCPU-INTRATE.json --iterations=1 --parameters="CopyCount=2,,,NumaProcessorAffinity=symmetric" --logger=csv

# Four INTRATE copies: CPUs 0-1 on node 0 and 48-49 on node 1.
./VirtualClient --profile=PERF-SPECCPU-INTRATE.json --iterations=1 --parameters="CopyCount=4,,,NumaProcessorAffinity=symmetric" --logger=csv

# Four FPRATE copies: the same two-per-node distribution.
./VirtualClient --profile=PERF-SPECCPU-FPRATE.json --iterations=1 --parameters="CopyCount=4,,,NumaProcessorAffinity=symmetric" --logger=csv
```

## PERF-SPECCPU-INTSPEED.json
Expand Down
Loading