diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index f7bba64fc7770..2898d8ec5886e 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -313,6 +313,7 @@ + diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Azure.Storage.DataMovement.Blobs.Perf.csproj b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Azure.Storage.DataMovement.Blobs.Perf.csproj index 81ac16c540ad3..83b2f84eed9d5 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Azure.Storage.DataMovement.Blobs.Perf.csproj +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Azure.Storage.DataMovement.Blobs.Perf.csproj @@ -8,8 +8,4 @@ - - - - diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Infrastructure/DirectoryTransferTest.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Infrastructure/DirectoryTransferTest.cs index fc6e09eec7776..1dc5dadeabc4f 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Infrastructure/DirectoryTransferTest.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Infrastructure/DirectoryTransferTest.cs @@ -7,26 +7,33 @@ using System.Threading; using System.Threading.Tasks; using Azure.Storage.Blobs; -using Azure.Storage.Tests.Shared; using Azure.Test.Perf; namespace Azure.Storage.DataMovement.Blobs.Perf { public abstract class DirectoryTransferTest : PerfTest where TOptions : DirectoryTransferOptions { - protected Random Random { get; } protected BlobServiceClient BlobServiceClient { get; } protected LocalFilesStorageResourceProvider LocalFileResourceProvider { get; } protected BlobsStorageResourceProvider BlobResourceProvider { get; } + + private TransferManager _transferManager; private TimeSpan _transferTimeout; public DirectoryTransferTest(TOptions options) : base(options) { - Random = new Random(); - BlobServiceClient = new BlobServiceClient(PerfTestEnvironment.Instance.BlobStorageEndpoint, PerfTestEnvironment.Instance.Credential); + BlobServiceClient = new BlobServiceClient(PerfTestEnvironment.Instance.StorageEndpoint, PerfTestEnvironment.Instance.Credential); LocalFileResourceProvider = new LocalFilesStorageResourceProvider(); BlobResourceProvider = new BlobsStorageResourceProvider(PerfTestEnvironment.Instance.Credential); - _transferTimeout = TimeSpan.FromSeconds(5 + (Options.Count * Options.Size) / (1 * 1024 * 1024)); + _transferTimeout = TimeSpan.FromSeconds(10 + (Options.Count * Options.Size) / (1 * 1024 * 1024)); + + TransferManagerOptions managerOptions = new() + { + ErrorHandling = DataTransferErrorMode.StopOnAnyFailure, + CheckpointerOptions = Options.DisableCheckpointer ? TransferCheckpointStoreOptions.Disabled() : default, + MaximumConcurrency = Options.Concurrency + }; + _transferManager = new TransferManager(managerOptions); } protected string CreateLocalDirectory(bool populate = false) @@ -39,7 +46,7 @@ protected string CreateLocalDirectory(bool populate = false) foreach (int i in Enumerable.Range(0, Options.Count)) { string filePath = Path.Combine(directory, $"file{i}"); - using (RepeatingStream stream = new(1024 * 1024, Options.Size, true)) + using (Stream stream = RandomStream.Create(Options.Size)) using (FileStream file = File.Open(filePath, FileMode.Create)) { stream.CopyTo(file); @@ -61,7 +68,7 @@ protected async Task CreateBlobContainerAsync(bool populate foreach (int i in Enumerable.Range(0, Options.Count)) { BlobClient blob = container.GetBlobClient($"blob{i}"); - using (RepeatingStream stream = new(1024 * 1024, Options.Size, true)) + using (Stream stream = RandomStream.Create(Options.Size)) { await blob.UploadAsync(stream); } @@ -76,13 +83,6 @@ protected async Task RunAndVerifyTransferAsync( StorageResource destination, CancellationToken cancellationToken) { - TransferManagerOptions managerOptions = new() - { - ErrorHandling = DataTransferErrorMode.StopOnAnyFailure, - CheckpointerOptions = Options.DisableCheckpointer ? TransferCheckpointStoreOptions.Disabled() : default - }; - TransferManager transferManager = new(managerOptions); - DataTransferOptions options = new() { CreationPreference = StorageResourceCreationPreference.OverwriteIfExists, @@ -90,7 +90,7 @@ protected async Task RunAndVerifyTransferAsync( MaximumTransferChunkSize = Options.ChunkSize, }; options.ItemTransferFailed += HandleFailure; - DataTransfer transfer = await transferManager.StartTransferAsync( + DataTransfer transfer = await _transferManager.StartTransferAsync( source, destination, options, cancellationToken); // The test runs for a specified duration and then cancels the token. diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Infrastructure/PerfTestEnvironment.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Infrastructure/PerfTestEnvironment.cs index 22fcef7a6da01..ecefb3e3f7304 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Infrastructure/PerfTestEnvironment.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Infrastructure/PerfTestEnvironment.cs @@ -25,19 +25,19 @@ internal sealed class PerfTestEnvironment : TestEnvironment /// The name of the Blob storage account to test against. /// /// The Blob storage account name, read from the "AZURE_STORAGE_ACCOUNT_NAME" environment variable. - public string BlobStorageAccountName => GetVariable("AZURE_STORAGE_ACCOUNT_NAME"); + public string StorageAccountName => GetVariable("AZURE_STORAGE_ACCOUNT_NAME"); /// /// The Blob storage endpoint. /// - public Uri BlobStorageEndpoint { get; } + public Uri StorageEndpoint { get; } /// /// Initializes a new instance of the class. /// public PerfTestEnvironment() { - BlobStorageEndpoint = new Uri($"https://{BlobStorageAccountName}.blob.{StorageEndpointSuffix}"); + StorageEndpoint = new Uri($"https://{StorageAccountName}.blob.{StorageEndpointSuffix}"); } } } diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Options/DirectoryTransferOptions.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Options/DirectoryTransferOptions.cs index 9f117615e3dfd..a7899f1964a52 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Options/DirectoryTransferOptions.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Azure.Storage.DataMovement.Blobs.Perf/Options/DirectoryTransferOptions.cs @@ -20,6 +20,9 @@ public class DirectoryTransferOptions : PerfOptions [Option("chunk-size", HelpText = "The chunk/block size to use during transfers (in bytes)")] public long? ChunkSize { get; set; } + [Option("concurrency", HelpText = "The max concurrency to use during each transfer.")] + public int? Concurrency { get; set; } + [Option("disable-checkpointer", HelpText = "Set to disable checkpointing.")] public bool DisableCheckpointer { get; set; } diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Infrastructure/DirectoryTransferTest.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Infrastructure/DirectoryTransferTest.cs new file mode 100644 index 0000000000000..fede2e9ebf14e --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Infrastructure/DirectoryTransferTest.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Azure.Test.Perf; +using Microsoft.Azure.Storage.Auth; +using Microsoft.Azure.Storage.Blob; + +namespace Microsoft.Azure.Storage.DataMovement.Perf +{ + public abstract class DirectoryTransferTest : PerfTest where TOptions : DirectoryTransferOptions + { + protected CloudBlobClient BlobClient; + + protected static DirectoryTransferContext DefaultTransferContext => new() + { + ShouldOverwriteCallbackAsync = DirectoryTransferContext.ForceOverwrite + }; + + public DirectoryTransferTest(TOptions options) : base(options) + { + StorageCredentials credentials = new( + PerfTestEnvironment.Instance.StorageAccountName, + PerfTestEnvironment.Instance.StorageAccountKey); + + CloudStorageAccount account = new(credentials, PerfTestEnvironment.Instance.StorageEndpointSuffix, useHttps: true); + BlobClient = account.CreateCloudBlobClient(); + + if (Options.ChunkSize.HasValue) + { + TransferManager.Configurations.BlockSize = (int)Options.ChunkSize.Value; + } + if (Options.Concurrency.HasValue) + { + TransferManager.Configurations.ParallelOperations = Options.Concurrency.Value; + } + } + + protected string CreateLocalDirectory(bool populate = false) + { + string directory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(directory); + + if (populate) + { + foreach (int i in Enumerable.Range(0, Options.Count)) + { + string filePath = Path.Combine(directory, $"file{i}"); + using (Stream stream = RandomStream.Create(Options.Size)) + using (FileStream file = System.IO.File.Open(filePath, FileMode.Create)) + { + stream.CopyTo(file); + } + } + } + + return directory; + } + + protected async Task CreateBlobContainerAsync(bool populate = false) + { + string containerName = $"test-{Guid.NewGuid()}".ToLowerInvariant(); + CloudBlobContainer container = BlobClient.GetContainerReference(containerName); + await container.CreateIfNotExistsAsync(); + + if (populate) + { + foreach (int i in Enumerable.Range(0, Options.Count)) + { + CloudBlockBlob blob = container.GetBlockBlobReference($"blob{i}"); + using (Stream stream = RandomStream.Create(Options.Size)) + { + await blob.UploadFromStreamAsync(stream); + } + } + } + + return container; + } + + protected void AssertTransferStatus(TransferStatus status) + { + if (status.NumberOfFilesSkipped > 0) + { + throw new Exception($"Transfer contained {status.NumberOfFilesSkipped} skipped files."); + } + if (status.NumberOfFilesFailed > 0) + { + throw new Exception($"Transfer contaiend {status.NumberOfFilesFailed} failed files."); + } + } + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Infrastructure/PerfTestEnvironment.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Infrastructure/PerfTestEnvironment.cs new file mode 100644 index 0000000000000..3ce3da7efcec6 --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Infrastructure/PerfTestEnvironment.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.TestFramework; + +namespace Microsoft.Azure.Storage.DataMovement.Perf +{ + /// + /// Represents the ambient environment in which the test suite is being run, offering access to information such as environment variables. + /// + internal sealed class PerfTestEnvironment : TestEnvironment + { + /// + /// The shared instance of the to be used during test runs. + /// + public static PerfTestEnvironment Instance { get; } = new PerfTestEnvironment(); + + /// + /// The storage account endpoint suffix to use for testing. + /// + public new string StorageEndpointSuffix => base.StorageEndpointSuffix ?? "core.windows.net"; + + /// + /// The name of the Blob storage account to test against. + /// + /// The Blob storage account name, read from the "AZURE_STORAGE_ACCOUNT_NAME" environment variable. + public string StorageAccountName => GetVariable("AZURE_STORAGE_ACCOUNT_NAME"); + + /// + /// The shared access key of the Blob storage account to test against. + /// + /// The Blob storage account key, read from the "AZURE_STORAGE_ACCOUNT_KEY" environment variable. + public string StorageAccountKey => GetVariable("AZURE_STORAGE_ACCOUNT_KEY"); + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Microsoft.Azure.Storage.DataMovement.Perf.csproj b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Microsoft.Azure.Storage.DataMovement.Perf.csproj new file mode 100644 index 0000000000000..4df0be2f92e4c --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Microsoft.Azure.Storage.DataMovement.Perf.csproj @@ -0,0 +1,13 @@ + + + Exe + + + + + + + + + + diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Options/DirectoryTransferOptions.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Options/DirectoryTransferOptions.cs new file mode 100644 index 0000000000000..e3816a782748e --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Options/DirectoryTransferOptions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Test.Perf; +using CommandLine; + +namespace Microsoft.Azure.Storage.DataMovement.Perf +{ + public class DirectoryTransferOptions : PerfOptions + { + [Option('c', "count", Default = 10, HelpText = "Number of items in each transfer.")] + public int Count { get; set; } + + [Option('s', "size", Default = 1024, HelpText = "Size of each file (in bytes)")] + public long Size { get; set; } + + [Option("chunk-size", HelpText = "The chunk/block size to use during transfers (in bytes)")] + public long? ChunkSize { get; set; } + + [Option("concurrency", HelpText = "The max concurrency to use during each transfer.")] + public int? Concurrency { get; set; } + + // Override warmup to set default to 0 + [Option('w', "warmup", Default = 0, HelpText = "Duration of warmup in seconds")] + public new int Warmup { get; set; } + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Program.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Program.cs new file mode 100644 index 0000000000000..1ad7284aaa19f --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Program.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Reflection; +using Azure.Test.Perf; + +await PerfProgram.Main(Assembly.GetEntryAssembly(), args); diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/CopyDirectory.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/CopyDirectory.cs new file mode 100644 index 0000000000000..0b2bb833ec97a --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/CopyDirectory.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Storage.Blob; + +namespace Microsoft.Azure.Storage.DataMovement.Perf +{ + public class CopyDirectory : DirectoryTransferTest + { + private CloudBlobContainer _sourceContainer; + private CloudBlobContainer _destinationContainer; + + public CopyDirectory(DirectoryTransferOptions options) : base(options) + { + } + + public override async Task GlobalSetupAsync() + { + await base.GlobalSetupAsync(); + _sourceContainer = await CreateBlobContainerAsync(populate: true); + _destinationContainer = await CreateBlobContainerAsync(); + } + + public override async Task GlobalCleanupAsync() + { + await _sourceContainer.DeleteIfExistsAsync(); + await _destinationContainer.DeleteIfExistsAsync(); + await base.GlobalCleanupAsync(); + } + + public override void Run(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override async Task RunAsync(CancellationToken cancellationToken) + { + TransferStatus transfer = await TransferManager.CopyDirectoryAsync( + _sourceContainer.GetDirectoryReference(string.Empty), + _destinationContainer.GetDirectoryReference(string.Empty), + CopyMethod.ServiceSideSyncCopy, + options: null, + DefaultTransferContext, + CancellationToken.None); // Don't pass cancellation token to let ransfer finish gracefully + AssertTransferStatus(transfer); + } + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/DownloadDirectory.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/DownloadDirectory.cs new file mode 100644 index 0000000000000..63f33b032c3f8 --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/DownloadDirectory.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Storage.Blob; + +namespace Microsoft.Azure.Storage.DataMovement.Perf +{ + public class DownloadDirectory : DirectoryTransferTest + { + private CloudBlobContainer _sourceContainer; + private string _destinationDirectory; + + public DownloadDirectory(DirectoryTransferOptions options) : base(options) + { + } + + public override async Task GlobalSetupAsync() + { + await base.GlobalSetupAsync(); + _sourceContainer = await CreateBlobContainerAsync(populate: true); + _destinationDirectory = CreateLocalDirectory(); + } + + public override async Task GlobalCleanupAsync() + { + await _sourceContainer.DeleteIfExistsAsync(); + System.IO.Directory.Delete(_destinationDirectory, true); + await base.GlobalCleanupAsync(); + } + + public override void Run(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override async Task RunAsync(CancellationToken cancellationToken) + { + TransferStatus transfer = await TransferManager.DownloadDirectoryAsync( + _sourceContainer.GetDirectoryReference(string.Empty), + _destinationDirectory, + null, + DefaultTransferContext, + CancellationToken.None); // Don't pass cancellation token to let ransfer finish gracefully + AssertTransferStatus(transfer); + } + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/UploadDirectory.cs b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/UploadDirectory.cs new file mode 100644 index 0000000000000..32d1a4074d684 --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Blobs/perf/Microsoft.Azure.Storage.DataMovement.Perf/Scenarios/UploadDirectory.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Storage.Blob; + +namespace Microsoft.Azure.Storage.DataMovement.Perf +{ + public class UploadDirectory : DirectoryTransferTest + { + private string _sourceDirectory; + private CloudBlobContainer _destinationContainer; + + public UploadDirectory(DirectoryTransferOptions options) : base(options) + { + } + + public override async Task GlobalSetupAsync() + { + await base.GlobalSetupAsync(); + _sourceDirectory = CreateLocalDirectory(populate: true); + _destinationContainer = await CreateBlobContainerAsync(); + } + + public override async Task GlobalCleanupAsync() + { + System.IO.Directory.Delete(_sourceDirectory, true); + await _destinationContainer.DeleteIfExistsAsync(); + await base.GlobalCleanupAsync(); + } + + public override void Run(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override async Task RunAsync(CancellationToken cancellationToken) + { + TransferStatus transfer = await TransferManager.UploadDirectoryAsync( + _sourceDirectory, + _destinationContainer.GetDirectoryReference(string.Empty), + options: null, + DefaultTransferContext, + CancellationToken.None); // Don't pass cancellation token to let ransfer finish gracefully + AssertTransferStatus(transfer); + } + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement.sln b/sdk/storage/Azure.Storage.DataMovement.sln index 7254f76b7e9ce..78e582d5b0c58 100644 --- a/sdk/storage/Azure.Storage.DataMovement.sln +++ b/sdk/storage/Azure.Storage.DataMovement.sln @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.DataMovement. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Test.Perf", "..\..\common\Perf\Azure.Test.Perf\Azure.Test.Perf.csproj", "{06787AFE-CA94-46EE-8F6F-0FD0C057022B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Storage.DataMovement.Perf", "Azure.Storage.DataMovement.Blobs\perf\Microsoft.Azure.Storage.DataMovement.Perf\Microsoft.Azure.Storage.DataMovement.Perf.csproj", "{AD9565FD-86A1-41A6-8B73-E7C2C66BF891}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -93,6 +95,10 @@ Global {06787AFE-CA94-46EE-8F6F-0FD0C057022B}.Debug|Any CPU.Build.0 = Debug|Any CPU {06787AFE-CA94-46EE-8F6F-0FD0C057022B}.Release|Any CPU.ActiveCfg = Release|Any CPU {06787AFE-CA94-46EE-8F6F-0FD0C057022B}.Release|Any CPU.Build.0 = Release|Any CPU + {AD9565FD-86A1-41A6-8B73-E7C2C66BF891}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD9565FD-86A1-41A6-8B73-E7C2C66BF891}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD9565FD-86A1-41A6-8B73-E7C2C66BF891}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD9565FD-86A1-41A6-8B73-E7C2C66BF891}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE