diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/DiskSpd/DiskSpdExecutorTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/DiskSpd/DiskSpdExecutorTests.cs index dffe0d439a..fc59f1ea66 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/DiskSpd/DiskSpdExecutorTests.cs +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/DiskSpd/DiskSpdExecutorTests.cs @@ -366,6 +366,166 @@ public async Task DiskSpdExecutorDeletesTestFilesByDefault() } } + [Test] + public void DiskSpdExecutorWithRawDiskTargetUsesPhysicalDeviceNumberSyntax_SingleProcessModel() + { + // Bare disks use VC's internal \\.\.PHYSICALDISK{N} identifier. + // The executor uses DiskSpd's native #N syntax (e.g. #1, #2) derived from disk.Index. + IEnumerable bareDisks = new List + { + this.CreateDisk(1, PlatformID.Win32NT, os: false, @"\\.\PHYSICALDISK1"), + this.CreateDisk(2, PlatformID.Win32NT, os: false, @"\\.\PHYSICALDISK2") + }; + + this.profileParameters[nameof(DiskSpdExecutor.RawDiskTarget)] = true; + this.profileParameters[nameof(DiskSpdExecutor.ProcessModel)] = WorkloadProcessModel.SingleProcess; + + List capturedArguments = new List(); + this.ProcessManager.OnCreateProcess = (exe, arguments, workingDir) => + { + capturedArguments.Add(arguments); + return new InMemoryProcess + { + StartInfo = new ProcessStartInfo { FileName = exe, Arguments = arguments } + }; + }; + + using (TestDiskSpdExecutor executor = new TestDiskSpdExecutor(this.Dependencies, this.profileParameters)) + { + executor.CreateWorkloadProcesses("diskspd.exe", "-b4K -r4K -t1 -o1 -w100", bareDisks, WorkloadProcessModel.SingleProcess); + } + + Assert.AreEqual(1, capturedArguments.Count); + // DiskSpd #N syntax -- derived from disk.Index, not disk.DevicePath. + // DiskSpd uses IOCTL_DISK_GET_DRIVE_GEOMETRY_EX internally; no -c needed. + Assert.IsTrue(capturedArguments[0].Contains(" #1")); + Assert.IsTrue(capturedArguments[0].Contains(" #2")); + // No file path style references should appear. + Assert.IsFalse(capturedArguments[0].Contains("diskspd-test.dat")); + } + + [Test] + public void DiskSpdExecutorWithRawDiskTargetUsesPhysicalDeviceNumberSyntax_SingleProcessPerDiskModel() + { + IEnumerable bareDisks = new List + { + this.CreateDisk(1, PlatformID.Win32NT, os: false, @"\\.\PHYSICALDISK1"), + this.CreateDisk(2, PlatformID.Win32NT, os: false, @"\\.\PHYSICALDISK2") + }; + + this.profileParameters[nameof(DiskSpdExecutor.RawDiskTarget)] = true; + + List capturedArguments = new List(); + int processCount = 0; + this.ProcessManager.OnCreateProcess = (exe, arguments, workingDir) => + { + capturedArguments.Add(arguments); + processCount++; + return new InMemoryProcess + { + StartInfo = new ProcessStartInfo { FileName = exe, Arguments = arguments } + }; + }; + + using (TestDiskSpdExecutor executor = new TestDiskSpdExecutor(this.Dependencies, this.profileParameters)) + { + executor.CreateWorkloadProcesses("diskspd.exe", "-b4K -r4K -t1 -o1 -w100", bareDisks, WorkloadProcessModel.SingleProcessPerDisk); + } + + Assert.AreEqual(2, processCount); + // Each process targets exactly one drive via DiskSpd's #N syntax (derived from disk.Index). + Assert.IsTrue(capturedArguments[0].Contains(" #1")); + Assert.IsFalse(capturedArguments[0].Contains(" #2")); + Assert.IsTrue(capturedArguments[1].Contains(" #2")); + Assert.IsFalse(capturedArguments[1].Contains(" #1")); + } + + [Test] + public void DiskSpdExecutorWithRawDiskTargetDoesNotAppendFilenamesToCommandLine() + { + Disk bareDisk = this.CreateDisk(1, PlatformID.Win32NT, os: false, @"\\.\PHYSICALDISK1"); + + this.profileParameters[nameof(DiskSpdExecutor.RawDiskTarget)] = true; + + string capturedArguments = null; + this.ProcessManager.OnCreateProcess = (exe, arguments, workingDir) => + { + capturedArguments = arguments; + return new InMemoryProcess + { + StartInfo = new ProcessStartInfo { FileName = exe, Arguments = arguments } + }; + }; + + using (TestDiskSpdExecutor executor = new TestDiskSpdExecutor(this.Dependencies, this.profileParameters)) + { + executor.CreateWorkloadProcesses( + "diskspd.exe", + "-b4K -r4K -t1 -o1 -w100", + new[] { bareDisk }, + WorkloadProcessModel.SingleProcess); + } + + Assert.IsNotNull(capturedArguments); + // DiskSpd's #N syntax is used -- derived from disk.Index=1. + // DiskSpd queries disk capacity via IOCTL; -c and \\.\PhysicalDriveN both cause errors. + Assert.IsTrue(capturedArguments.Contains(" #1")); + // No test-file extension should be present. + Assert.IsFalse(capturedArguments.Contains(".dat")); + } + + [Test] + public void DiskSpdExecutorWithRawDiskTargetStoresDeviceNumberPathsInTestFiles() + { + // TestFiles is iterated by DeleteTestFilesAsync. For raw disk targets the paths must be + // the #N device number strings -- not file paths and not \\.\.PhysicalDriveN. + // File.Exists("#1") returns false, so DeleteTestFilesAsync becomes a correct no-op. + IEnumerable bareDisks = new List + { + this.CreateDisk(1, PlatformID.Win32NT, os: false, @"\\.\.PHYSICALDISK1"), + this.CreateDisk(2, PlatformID.Win32NT, os: false, @"\\.\.PHYSICALDISK2") + }; + + this.profileParameters[nameof(DiskSpdExecutor.RawDiskTarget)] = true; + + this.ProcessManager.OnCreateProcess = (exe, arguments, workingDir) => + new InMemoryProcess { StartInfo = new ProcessStartInfo { FileName = exe, Arguments = arguments } }; + + IEnumerable processes; + using (TestDiskSpdExecutor executor = new TestDiskSpdExecutor(this.Dependencies, this.profileParameters)) + { + processes = executor.CreateWorkloadProcesses( + "diskspd.exe", "-b4K -r4K -t1 -o1 -w100", bareDisks, WorkloadProcessModel.SingleProcessPerDisk).ToList(); + } + + Assert.AreEqual(2, processes.Count()); + CollectionAssert.AreEqual(new[] { "#1" }, processes.ElementAt(0).TestFiles); + CollectionAssert.AreEqual(new[] { "#2" }, processes.ElementAt(1).TestFiles); + } + + [Test] + public void DiskSpdExecutorRawDiskTargetDefaultsToFalse() + { + using (TestDiskSpdExecutor executor = new TestDiskSpdExecutor(this.Dependencies, this.profileParameters)) + { + Assert.IsFalse(executor.RawDiskTarget); + } + } + + [Test] + public void DiskSpdExecutorThrowsWhenBothRawDiskTargetAndDiskFillAreEnabled() + { + this.profileParameters[nameof(DiskSpdExecutor.RawDiskTarget)] = true; + this.profileParameters[nameof(DiskSpdExecutor.DiskFill)] = true; + this.profileParameters[nameof(DiskSpdExecutor.DiskFillSize)] = "500G"; + + using (TestDiskSpdExecutor executor = new TestDiskSpdExecutor(this.Dependencies, this.profileParameters)) + { + WorkloadException exc = Assert.Throws(() => executor.Validate()); + Assert.AreEqual(ErrorReason.InvalidProfileDefinition, exc.Reason); + } + } + private IEnumerable SetupWorkloadScenario( bool testRemoteDisks = false, bool testOSDisk = false, string processModel = WorkloadProcessModel.SingleProcess) { diff --git a/src/VirtualClient/VirtualClient.Actions/DiskSpd/DiskSpdExecutor.cs b/src/VirtualClient/VirtualClient.Actions/DiskSpd/DiskSpdExecutor.cs index 0ee10a2e34..1bd5167619 100644 --- a/src/VirtualClient/VirtualClient.Actions/DiskSpd/DiskSpdExecutor.cs +++ b/src/VirtualClient/VirtualClient.Actions/DiskSpd/DiskSpdExecutor.cs @@ -181,6 +181,25 @@ public string ProcessModel } } + /// + /// True/false whether to target the raw physical device path directly (e.g. \\.\PhysicalDrive1) + /// instead of a test file on a mounted volume. Use this for bare disk (unformatted) scenarios. + /// When enabled the step is skipped and no test file path is appended to + /// the DiskSpd command line — the device path is passed instead. + /// + public bool RawDiskTarget + { + get + { + return this.Parameters.GetValue(nameof(this.RawDiskTarget), false); + } + + set + { + this.Parameters[nameof(this.RawDiskTarget)] = value; + } + } + /// /// The disk I/O queue depth to use for running disk I/O operations. /// Default = 16. @@ -311,8 +330,22 @@ protected override async Task CleanupAsync(EventContext telemetryContext, Cancel /// The disks under test. protected DiskWorkloadProcess CreateWorkloadProcess(string executable, string commandArguments, string testedInstance, params Disk[] disksToTest) { - string[] testFiles = disksToTest.Select(disk => this.GetTestFiles(disk.GetPreferredAccessPath(this.Platform))).ToArray(); - string diskSpdArguments = $"{commandArguments} {string.Join(" ", testFiles)}"; + string diskSpdArguments; + string[] testFiles; + + if (this.RawDiskTarget) + { + // DiskSpd has a native syntax for targeting a physical drive by its index: #. + // This is the correct format for raw physical disk access; DiskSpd uses + // IOCTL_DISK_GET_DRIVE_GEOMETRY_EX internally to determine the drive capacity. + testFiles = disksToTest.Select(disk => $"#{disk.Index}").ToArray(); + diskSpdArguments = $"{commandArguments} {string.Join(" ", testFiles)}"; + } + else + { + testFiles = disksToTest.Select(disk => this.GetTestFiles(disk.GetPreferredAccessPath(this.Platform))).ToArray(); + diskSpdArguments = $"{commandArguments} {string.Join(" ", testFiles)}"; + } IProcessProxy process = this.SystemManagement.ProcessManager.CreateProcess(executable, diskSpdArguments); @@ -667,6 +700,15 @@ protected override void Validate() $"to be defined (e.g. 496G).", ErrorReason.InvalidProfileDefinition); } + + if (this.RawDiskTarget && this.DiskFill) + { + throw new WorkloadException( + $"Invalid profile definition. The '{nameof(DiskSpdExecutor.DiskFill)}' option cannot be used together with " + + $"'{nameof(DiskSpdExecutor.RawDiskTarget)}'. Disk fill operations create test files on a mounted volume and are " + + $"not applicable to raw physical device access.", + ErrorReason.InvalidProfileDefinition); + } } private void CaptureMetrics(DiskWorkloadProcess workload, EventContext telemetryContext) diff --git a/src/VirtualClient/VirtualClient.Contracts.UnitTests/DiskExtensionsTests.cs b/src/VirtualClient/VirtualClient.Contracts.UnitTests/DiskExtensionsTests.cs index 8c32998dd6..8b5245ec20 100644 --- a/src/VirtualClient/VirtualClient.Contracts.UnitTests/DiskExtensionsTests.cs +++ b/src/VirtualClient/VirtualClient.Contracts.UnitTests/DiskExtensionsTests.cs @@ -189,5 +189,52 @@ public void GetDefaultMountPointNameExtensionUsesASpecificPrefixWhenProvided() Assert.AreEqual(expectedMountPointName, actualMountPointName); } } + + [Test] + public void GetPreferredAccessPathThrowsForBareDiskWithNoVolumes_Windows() + { + // GetPreferredAccessPath is for file-based workloads only. A disk with no volumes + // cannot be used for file I/O, so the original throw behavior is correct. + // Raw disk access bypasses this method entirely via RawDiskTarget in DiskSpdExecutor. + Disk bareDisk = this.fixture.CreateDisk(1, PlatformID.Win32NT, os: false, @"\\.\PHYSICALDISK1"); + + Assert.Throws(() => bareDisk.GetPreferredAccessPath(PlatformID.Win32NT)); + } + + [Test] + public void GetPreferredAccessPathThrowsForBareDiskWithNoVolumes_Unix() + { + // Same as Windows: a bare Linux disk with no volumes cannot be used for file I/O. + Disk bareDisk = this.fixture.CreateDisk(1, PlatformID.Unix, os: false, @"/dev/sdb"); + + Assert.Throws(() => bareDisk.GetPreferredAccessPath(PlatformID.Unix)); + } + + [Test] + public void GetPreferredAccessPathReturnsVolumeMountPointForFormattedNonOsDisk_Windows() + { + // A formatted non-OS Windows disk with a volume should return the volume access path. + this.disks = this.fixture.CreateDisks(PlatformID.Win32NT, true); + Disk dataDisk = this.disks.First(d => !d.IsOperatingSystem()); + + string path = dataDisk.GetPreferredAccessPath(PlatformID.Win32NT); + string expectedPath = dataDisk.Volumes.First().AccessPaths.First(); + + Assert.AreEqual(expectedPath, path); + } + + [Test] + public void GetPreferredAccessPathReturnsVolumeMountPointForFormattedNonOsDisk_Unix() + { + this.disks = this.fixture.CreateDisks(PlatformID.Unix, true); + Disk dataDisk = this.disks.First(d => !d.IsOperatingSystem()); + + string path = dataDisk.GetPreferredAccessPath(PlatformID.Unix); + string expectedPath = dataDisk.Volumes + .OrderByDescending(v => v.SizeInBytes(PlatformID.Unix)) + .First().AccessPaths.First(); + + Assert.AreEqual(expectedPath, path); + } } } diff --git a/src/VirtualClient/VirtualClient.Contracts.UnitTests/DiskFiltersTests.cs b/src/VirtualClient/VirtualClient.Contracts.UnitTests/DiskFiltersTests.cs index a6a9179616..d322b63c37 100644 --- a/src/VirtualClient/VirtualClient.Contracts.UnitTests/DiskFiltersTests.cs +++ b/src/VirtualClient/VirtualClient.Contracts.UnitTests/DiskFiltersTests.cs @@ -524,5 +524,69 @@ public void DiskFiltersHandlesAnomaliesEncounters_1() Assert.AreEqual("/dev/sdi", filteredDisks.ElementAt(30).DevicePath); Assert.AreEqual("/dev/sdj", filteredDisks.ElementAt(31).DevicePath); } + + [Test] + public void DiskFiltersIncludeOfflineFilterKeepsOfflineDisksOnWindows() + { + // Arrange: create 4 disks; mark one as offline. + this.disks = this.mockFixture.CreateDisks(PlatformID.Win32NT, true); + this.disks.ElementAt(0).Properties["Status"] = "Online"; + this.disks.ElementAt(1).Properties["Status"] = "Online"; + this.disks.ElementAt(2).Properties["Status"] = "Offline (Policy)"; + this.disks.ElementAt(3).Properties["Status"] = "Online"; + + // "none" alone would remove the offline disk; "none&IncludeOffline" should retain it. + string filterString = "none&IncludeOffline"; + IEnumerable result = DiskFilters.FilterDisks(this.disks, filterString, PlatformID.Win32NT); + + Assert.AreEqual(4, result.Count()); + Assert.IsTrue(result.Any(d => d.Properties["Status"].ToString().Contains("Offline"))); + } + + [Test] + public void DiskFiltersIncludeOfflineFilterIsCaseInsensitive() + { + this.disks = this.mockFixture.CreateDisks(PlatformID.Win32NT, true); + this.disks.ElementAt(1).Properties["Status"] = "Offline"; + + // All casing variants should be accepted. + foreach (string variant in new[] { "none&IncludeOffline", "none&includeoffline", "none&INCLUDEOFFLINE" }) + { + IEnumerable result = DiskFilters.FilterDisks(this.disks, variant, PlatformID.Win32NT); + Assert.AreEqual(4, result.Count(), $"Expected offline disk retained for filter '{variant}'"); + } + } + + [Test] + public void DiskFiltersWithoutIncludeOfflineDoesNotRetainOfflineDisksOnWindows() + { + this.disks = this.mockFixture.CreateDisks(PlatformID.Win32NT, true); + this.disks.ElementAt(2).Properties["Status"] = "Offline (Policy)"; + + // Default behaviour: the offline disk is excluded. + IEnumerable result = DiskFilters.FilterDisks(this.disks, "none", PlatformID.Win32NT); + + Assert.AreEqual(3, result.Count()); + Assert.IsFalse(result.Any(d => d.Properties.ContainsKey("Status") && + d.Properties["Status"].ToString().Contains("Offline"))); + } + + [Test] + public void DiskFiltersIncludeOfflineCanBeCombinedWithBiggestSizeFilter() + { + this.disks = this.mockFixture.CreateDisks(PlatformID.Win32NT, true); + // Make the offline disk the biggest. + this.disks.ElementAt(0).Properties["Size"] = "100 GB"; + this.disks.ElementAt(1).Properties["Size"] = "100 GB"; + this.disks.ElementAt(2).Properties["Size"] = "2000 GB"; // offline + biggest + this.disks.ElementAt(2).Properties["Status"] = "Offline"; + this.disks.ElementAt(3).Properties["Size"] = "100 GB"; + + string filterString = "BiggestSize&IncludeOffline"; + IEnumerable result = DiskFilters.FilterDisks(this.disks, filterString, PlatformID.Win32NT); + + Assert.AreEqual(1, result.Count()); + Assert.IsTrue(object.ReferenceEquals(this.disks.ElementAt(2), result.First())); + } } } diff --git a/src/VirtualClient/VirtualClient.Contracts/DiskFilters.cs b/src/VirtualClient/VirtualClient.Contracts/DiskFilters.cs index 9fa9682e54..45ba8a08fd 100644 --- a/src/VirtualClient/VirtualClient.Contracts/DiskFilters.cs +++ b/src/VirtualClient/VirtualClient.Contracts/DiskFilters.cs @@ -31,8 +31,15 @@ public static IEnumerable FilterDisks(IEnumerable disks, string filt // filterName1:value1&filterName2:value2&filterDoesNotRequireValue&filter4:value4 List filters = filterString.Split("&", StringSplitOptions.RemoveEmptyEntries).ToList(); + // Allow callers to opt into keeping offline disks (e.g. bare/unformatted disks for raw disk I/O). + bool includeOffline = filters.Any(f => f.Trim().Equals(Filters.IncludeOffline, StringComparison.OrdinalIgnoreCase)); + disks = DiskFilters.FilterStoragePathByPrefix(disks, platform); - disks = DiskFilters.FilterOfflineDisksOnWindows(disks, platform); + if (!includeOffline) + { + disks = DiskFilters.FilterOfflineDisksOnWindows(disks, platform); + } + disks = DiskFilters.FilterReadOnlyDisksOnWindows(disks, platform); foreach (string filter in filters) @@ -86,6 +93,10 @@ public static IEnumerable FilterDisks(IEnumerable disks, string filt disks = DiskFilters.DiskPathFilter(disks, filterValue); break; + case Filters.IncludeOffline: + // Already handled before the filter loop; treated as a no-op here. + break; + default: throw new EnvironmentSetupException($"Disk filter '{filter}' is not supported.", ErrorReason.DiskInformationNotAvailable); } @@ -248,6 +259,12 @@ private static class Filters /// Disk path filter. /// public const string DiskPath = "diskpath"; + + /// + /// Include offline disks filter. By default offline disks are excluded on Windows. + /// Use this filter to include them (e.g. bare/unformatted disks for raw disk I/O). + /// + public const string IncludeOffline = "includeoffline"; } } } diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-IO-DISKSPD-RAWDISK.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-IO-DISKSPD-RAWDISK.json new file mode 100644 index 0000000000..2a664f5d84 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-IO-DISKSPD-RAWDISK.json @@ -0,0 +1,291 @@ +{ + "Description": "DiskSpd I/O Stress Performance Workload (Raw Disk / Bare Metal)", + "Metadata": { + "RecommendedMinimumExecutionTime": "00:30:00", + "SupportedPlatforms": "win-x64,win-arm64", + "SupportedOperatingSystems": "Windows" + }, + "Parameters": { + "DiskFilter": "OsDisk:false&IncludeOffline", + "Duration": "00:05:00", + "ThreadCount": "{calculate({LogicalCoreCount}/2)}", + "QueueDepth": "{calculate(512/{ThreadCount})}", + "ProcessModel": "SingleProcess" + }, + "Actions": [ + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "RandomWrite_4k_BlockSize", + "MetricScenario": "diskspd_rawdisk_randwrite_4k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b4K -r4K -t{ThreadCount} -o{QueueDepth} -w100 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,randwrite,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "RandomWrite_8k_BlockSize", + "MetricScenario": "diskspd_rawdisk_randwrite_8k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b8K -r4K -t{ThreadCount} -o{QueueDepth} -w100 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,randwrite,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "RandomWrite_16k_BlockSize", + "MetricScenario": "diskspd_rawdisk_randwrite_16k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b16K -r4K -t{ThreadCount} -o{QueueDepth} -w100 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,randwrite,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "RandomWrite_1024k_BlockSize", + "MetricScenario": "diskspd_rawdisk_randwrite_1024k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b1024k -r4K -t{ThreadCount} -o{QueueDepth} -w100 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,randwrite,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "SequentialWrite_4k_BlockSize", + "MetricScenario": "diskspd_rawdisk_write_4k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b4K -si4K -t{ThreadCount} -o{QueueDepth} -w100 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,write,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "SequentialWrite_8k_BlockSize", + "MetricScenario": "diskspd_rawdisk_write_8k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b8K -si4K -t{ThreadCount} -o{QueueDepth} -w100 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,write,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "SequentialWrite_16k_BlockSize", + "MetricScenario": "diskspd_rawdisk_write_16k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b16K -si4K -t{ThreadCount} -o{QueueDepth} -w100 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,write,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "SequentialWrite_1024k_BlockSize", + "MetricScenario": "diskspd_rawdisk_write_1024k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b1024k -si4K -t{ThreadCount} -o{QueueDepth} -w100 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,write,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "RandomRead_4k_BlockSize", + "MetricScenario": "diskspd_rawdisk_randread_4k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b4K -r4K -t{ThreadCount} -o{QueueDepth} -w0 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,randread,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "RandomRead_8k_BlockSize", + "MetricScenario": "diskspd_rawdisk_randread_8k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b8K -r4K -t{ThreadCount} -o{QueueDepth} -w0 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,randread,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "RandomRead_16k_BlockSize", + "MetricScenario": "diskspd_rawdisk_randread_16k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b16K -r4K -t{ThreadCount} -o{QueueDepth} -w0 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,randread,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "RandomRead_1024k_BlockSize", + "MetricScenario": "diskspd_rawdisk_randread_1024k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b1024k -r4K -t{ThreadCount} -o{QueueDepth} -w0 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,randread,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "SequentialRead_4k_BlockSize", + "MetricScenario": "diskspd_rawdisk_read_4k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b4K -si4K -t{ThreadCount} -o{QueueDepth} -w0 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,read,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "SequentialRead_8k_BlockSize", + "MetricScenario": "diskspd_rawdisk_read_8k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b8K -si4K -t{ThreadCount} -o{QueueDepth} -w0 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,read,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "SequentialRead_16k_BlockSize", + "MetricScenario": "diskspd_rawdisk_read_16k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b16K -si4K -t{ThreadCount} -o{QueueDepth} -w0 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,read,rawdisk" + } + }, + { + "Type": "DiskSpdExecutor", + "Parameters": { + "Scenario": "SequentialRead_1024k_BlockSize", + "MetricScenario": "diskspd_rawdisk_read_1024k_d{QueueDepth}_th{ThreadCount}", + "PackageName": "diskspd", + "DiskFilter": "$.Parameters.DiskFilter", + "CommandLine": "-b1024k -si4K -t{ThreadCount} -o{QueueDepth} -w0 -d{Duration.TotalSeconds} -Suw -W15 -D -L -Rtext", + "Duration": "$.Parameters.Duration", + "ThreadCount": "$.Parameters.ThreadCount", + "QueueDepth": "$.Parameters.QueueDepth", + "ProcessModel": "$.Parameters.ProcessModel", + "RawDiskTarget": true, + "Tags": "IO,DiskSpd,read,rawdisk" + } + } + ], + "Dependencies": [ + { + "Type": "SetDiskSanPolicy", + "Parameters": { + "Scenario": "SetSanPolicyOnlineAll" + } + }, + { + "Type": "DependencyPackageInstallation", + "Parameters": { + "Scenario": "InstallDiskSpdPackage", + "BlobContainer": "packages", + "BlobName": "diskspd.2.0.21.zip", + "PackageName": "diskspd", + "Extract": true + } + } + ] +}