diff --git a/performance/benchmark/Descriptions.cs b/performance/benchmark/Descriptions.cs index 8e01d91da..6c051a3ad 100644 --- a/performance/benchmark/Descriptions.cs +++ b/performance/benchmark/Descriptions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Net.Http; @@ -15,88 +15,112 @@ namespace performance; [ShortRunJob] public class Descriptions { - [Benchmark] - public async Task PetStoreYaml() + private enum DescriptionSource { - return await ParseDocumentAsync(PetStoreYamlPath); + PetStoreYaml, + PetStoreJson, + GHESYaml, + GHESJson, + GHESNextYaml, + GHESNextJson } + [Benchmark] - public async Task PetStoreJson() - { - return await ParseDocumentAsync(PetStoreJsonPath, OpenApiConstants.Json); - } + public Task PetStoreYaml() => ParseDocumentAsync(DescriptionSource.PetStoreYaml); + [Benchmark] - public async Task GHESYaml() - { - return await ParseDocumentAsync(GHESYamlDescriptionUrl); - } + public Task PetStoreJson() => ParseDocumentAsync(DescriptionSource.PetStoreJson, OpenApiConstants.Json); + [Benchmark] - public async Task GHESJson() - { - return await ParseDocumentAsync(GHESJsonDescriptionUrl, OpenApiConstants.Json); - } + public Task GHESYaml() => ParseDocumentAsync(DescriptionSource.GHESYaml); + [Benchmark] - public async Task GHESNextYaml() - { - return await ParseDocumentAsync(GHESNextYamlDescriptionUrl); - } + public Task GHESJson() => ParseDocumentAsync(DescriptionSource.GHESJson, OpenApiConstants.Json); + [Benchmark] - public async Task GHESNextJson() - { - return await ParseDocumentAsync(GHESNextJsonDescriptionUrl, OpenApiConstants.Json); - } - private readonly Dictionary _streams = new(StringComparer.OrdinalIgnoreCase); + public Task GHESNextYaml() => ParseDocumentAsync(DescriptionSource.GHESNextYaml); + + [Benchmark] + public Task GHESNextJson() => ParseDocumentAsync(DescriptionSource.GHESNextJson, OpenApiConstants.Json); + + private readonly Dictionary _streams = new(capacity: 6); + [GlobalSetup] public async Task GetAllDescriptions() { - _httpClient = new HttpClient(); - readerSettings = new OpenApiReaderSettings + _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(60) }; + _readerSettings = new OpenApiReaderSettings { LeaveStreamOpen = true }; + _readerSettings.AddYamlReader(); + + var results = await Task.WhenAll( + LoadFromAssemblyAsync(DescriptionSource.PetStoreYaml, PetStoreYamlResourceName), + LoadFromAssemblyAsync(DescriptionSource.PetStoreJson, PetStoreJsonResourceName), + LoadFromUrlAsync(DescriptionSource.GHESYaml, GHESYamlDescriptionUrl), + LoadFromUrlAsync(DescriptionSource.GHESJson, GHESJsonDescriptionUrl), + LoadFromUrlAsync(DescriptionSource.GHESNextYaml, GHESNextYamlDescriptionUrl), + LoadFromUrlAsync(DescriptionSource.GHESNextJson, GHESNextJsonDescriptionUrl) + ).ConfigureAwait(false); + + foreach (var (source, stream) in results) { - LeaveStreamOpen = true, - }; - readerSettings.AddYamlReader(); - await LoadDocumentFromAssemblyIntoStreams(PetStoreYamlPath); - await LoadDocumentFromAssemblyIntoStreams(PetStoreJsonPath); - await LoadDocumentFromUrlIntoStreams(GHESYamlDescriptionUrl); - await LoadDocumentFromUrlIntoStreams(GHESJsonDescriptionUrl); - await LoadDocumentFromUrlIntoStreams(GHESNextYamlDescriptionUrl); - await LoadDocumentFromUrlIntoStreams(GHESNextJsonDescriptionUrl); + _streams.Add(source, stream); + } } - private OpenApiReaderSettings readerSettings; - private const string PetStoreYamlPath = @"petStore.yaml"; - private const string PetStoreJsonPath = @"petStore.json"; - private const string GHESYamlDescriptionUrl = @"https://raw.githubusercontent.com/github/rest-api-description/aef5e31a2d10fdaab311ec6d18a453021a81383d/descriptions/ghes-3.16/ghes-3.16.2022-11-28.yaml"; - private const string GHESJsonDescriptionUrl = @"https://raw.githubusercontent.com/github/rest-api-description/aef5e31a2d10fdaab311ec6d18a453021a81383d/descriptions/ghes-3.16/ghes-3.16.2022-11-28.json"; - private const string GHESNextYamlDescriptionUrl = @"https://raw.githubusercontent.com/github/rest-api-description/aef5e31a2d10fdaab311ec6d18a453021a81383d/descriptions-next/ghes-3.16/ghes-3.16.2022-11-28.yaml"; - private const string GHESNextJsonDescriptionUrl = @"https://raw.githubusercontent.com/github/rest-api-description/aef5e31a2d10fdaab311ec6d18a453021a81383d/descriptions-next/ghes-3.16/ghes-3.16.2022-11-28.json"; - private async Task ParseDocumentAsync(string fileName, string format = null) + + private OpenApiReaderSettings _readerSettings; + + private const string PetStoreYamlResourceName = "petStore.yaml"; + private const string PetStoreJsonResourceName = "petStore.json"; + + private const string GHESRepoCommitSha = "aef5e31a2d10fdaab311ec6d18a453021a81383d"; + private const string GHESReleaseVersion = "ghes-3.16"; + private const string GHESDescriptionFileName = "ghes-3.16.2022-11-28"; + + // Building the four GHES URLs from shared constants means the pinned commit only needs to + // change in one place when the benchmark data set is refreshed, instead of four near-identical + // literals that can silently drift out of sync with each other. + private static string BuildGHESDescriptionUrl(string descriptionsFolder, string extension) => + $"https://raw.githubusercontent.com/github/rest-api-description/{GHESRepoCommitSha}/{descriptionsFolder}/{GHESReleaseVersion}/{GHESDescriptionFileName}.{extension}"; + + private static readonly string GHESYamlDescriptionUrl = BuildGHESDescriptionUrl("descriptions", "yaml"); + private static readonly string GHESJsonDescriptionUrl = BuildGHESDescriptionUrl("descriptions", "json"); + private static readonly string GHESNextYamlDescriptionUrl = BuildGHESDescriptionUrl("descriptions-next", "yaml"); + private static readonly string GHESNextJsonDescriptionUrl = BuildGHESDescriptionUrl("descriptions-next", "json"); + + private async Task ParseDocumentAsync(DescriptionSource source, string format = null) { format ??= OpenApiConstants.Yaml; - var stream = _streams[fileName]; + var stream = _streams[source]; stream.Seek(0, SeekOrigin.Begin); - - var (document, _) = await OpenApiDocument.LoadAsync(stream, format, readerSettings).ConfigureAwait(false); + + var (document, _) = await OpenApiDocument.LoadAsync(stream, format, _readerSettings).ConfigureAwait(false); return document; } + private HttpClient _httpClient; - private async Task LoadDocumentFromUrlIntoStreams(string url) + + private async Task<(DescriptionSource Source, MemoryStream Stream)> LoadFromUrlAsync(DescriptionSource source, string url) { - var response = await _httpClient.GetAsync(url).ConfigureAwait(false); + using var response = await _httpClient.GetAsync(url).ConfigureAwait(false); response.EnsureSuccessStatusCode(); - var stream = new MemoryStream(); // NOT disposed on purpose + + var stream = new MemoryStream(); await response.Content.CopyToAsync(stream).ConfigureAwait(false); stream.Seek(0, SeekOrigin.Begin); - _streams.Add(url, stream); + return (source, stream); } - private static readonly Assembly assembly = typeof(Descriptions).GetTypeInfo().Assembly; - private async Task LoadDocumentFromAssemblyIntoStreams(string fileName) + + private static readonly Assembly _assembly = typeof(Descriptions).Assembly; + + private async Task<(DescriptionSource Source, MemoryStream Stream)> LoadFromAssemblyAsync(DescriptionSource source, string resourceFileName) { - using var resource = assembly.GetManifestResourceStream($"PerformanceTests.{fileName}"); - var stream = new MemoryStream(); // NOT disposed on purpose - await resource.CopyToAsync(stream).ConfigureAwait(false); + using var resourceStream = _assembly.GetManifestResourceStream($"PerformanceTests.{resourceFileName}"); + var stream = new MemoryStream(); + await resourceStream.CopyToAsync(stream).ConfigureAwait(false); stream.Seek(0, SeekOrigin.Begin); - _streams.Add(fileName, stream); + return (source, stream); } + [GlobalCleanup] public void Cleanup() {