1- using System ;
1+ using System ;
22using System . Collections . Generic ;
33using System . IO ;
44using System . Net . Http ;
@@ -15,88 +15,112 @@ namespace performance;
1515[ ShortRunJob ]
1616public class Descriptions
1717{
18- [ Benchmark ]
19- public async Task < OpenApiDocument > PetStoreYaml ( )
18+ private enum DescriptionSource
2019 {
21- return await ParseDocumentAsync ( PetStoreYamlPath ) ;
20+ PetStoreYaml ,
21+ PetStoreJson ,
22+ GHESYaml ,
23+ GHESJson ,
24+ GHESNextYaml ,
25+ GHESNextJson
2226 }
27+
2328 [ Benchmark ]
24- public async Task < OpenApiDocument > PetStoreJson ( )
25- {
26- return await ParseDocumentAsync ( PetStoreJsonPath , OpenApiConstants . Json ) ;
27- }
29+ public Task < OpenApiDocument > PetStoreYaml ( ) => ParseDocumentAsync ( DescriptionSource . PetStoreYaml ) ;
30+
2831 [ Benchmark ]
29- public async Task < OpenApiDocument > GHESYaml ( )
30- {
31- return await ParseDocumentAsync ( GHESYamlDescriptionUrl ) ;
32- }
32+ public Task < OpenApiDocument > PetStoreJson ( ) => ParseDocumentAsync ( DescriptionSource . PetStoreJson , OpenApiConstants . Json ) ;
33+
3334 [ Benchmark ]
34- public async Task < OpenApiDocument > GHESJson ( )
35- {
36- return await ParseDocumentAsync ( GHESJsonDescriptionUrl , OpenApiConstants . Json ) ;
37- }
35+ public Task < OpenApiDocument > GHESYaml ( ) => ParseDocumentAsync ( DescriptionSource . GHESYaml ) ;
36+
3837 [ Benchmark ]
39- public async Task < OpenApiDocument > GHESNextYaml ( )
40- {
41- return await ParseDocumentAsync ( GHESNextYamlDescriptionUrl ) ;
42- }
38+ public Task < OpenApiDocument > GHESJson ( ) => ParseDocumentAsync ( DescriptionSource . GHESJson , OpenApiConstants . Json ) ;
39+
4340 [ Benchmark ]
44- public async Task < OpenApiDocument > GHESNextJson ( )
45- {
46- return await ParseDocumentAsync ( GHESNextJsonDescriptionUrl , OpenApiConstants . Json ) ;
47- }
48- private readonly Dictionary < string , MemoryStream > _streams = new ( StringComparer . OrdinalIgnoreCase ) ;
41+ public Task < OpenApiDocument > GHESNextYaml ( ) => ParseDocumentAsync ( DescriptionSource . GHESNextYaml ) ;
42+
43+ [ Benchmark ]
44+ public Task < OpenApiDocument > GHESNextJson ( ) => ParseDocumentAsync ( DescriptionSource . GHESNextJson , OpenApiConstants . Json ) ;
45+
46+ private readonly Dictionary < DescriptionSource , MemoryStream > _streams = new ( capacity : 6 ) ;
47+
4948 [ GlobalSetup ]
5049 public async Task GetAllDescriptions ( )
5150 {
52- _httpClient = new HttpClient ( ) ;
53- readerSettings = new OpenApiReaderSettings
51+ _httpClient = new HttpClient { Timeout = TimeSpan . FromSeconds ( 60 ) } ;
52+ _readerSettings = new OpenApiReaderSettings { LeaveStreamOpen = true } ;
53+ _readerSettings . AddYamlReader ( ) ;
54+
55+ var results = await Task . WhenAll (
56+ LoadFromAssemblyAsync ( DescriptionSource . PetStoreYaml , PetStoreYamlResourceName ) ,
57+ LoadFromAssemblyAsync ( DescriptionSource . PetStoreJson , PetStoreJsonResourceName ) ,
58+ LoadFromUrlAsync ( DescriptionSource . GHESYaml , GHESYamlDescriptionUrl ) ,
59+ LoadFromUrlAsync ( DescriptionSource . GHESJson , GHESJsonDescriptionUrl ) ,
60+ LoadFromUrlAsync ( DescriptionSource . GHESNextYaml , GHESNextYamlDescriptionUrl ) ,
61+ LoadFromUrlAsync ( DescriptionSource . GHESNextJson , GHESNextJsonDescriptionUrl )
62+ ) . ConfigureAwait ( false ) ;
63+
64+ foreach ( var ( source , stream ) in results )
5465 {
55- LeaveStreamOpen = true ,
56- } ;
57- readerSettings . AddYamlReader ( ) ;
58- await LoadDocumentFromAssemblyIntoStreams ( PetStoreYamlPath ) ;
59- await LoadDocumentFromAssemblyIntoStreams ( PetStoreJsonPath ) ;
60- await LoadDocumentFromUrlIntoStreams ( GHESYamlDescriptionUrl ) ;
61- await LoadDocumentFromUrlIntoStreams ( GHESJsonDescriptionUrl ) ;
62- await LoadDocumentFromUrlIntoStreams ( GHESNextYamlDescriptionUrl ) ;
63- await LoadDocumentFromUrlIntoStreams ( GHESNextJsonDescriptionUrl ) ;
66+ _streams . Add ( source , stream ) ;
67+ }
6468 }
65- private OpenApiReaderSettings readerSettings ;
66- private const string PetStoreYamlPath = @"petStore.yaml" ;
67- private const string PetStoreJsonPath = @"petStore.json" ;
68- private const string GHESYamlDescriptionUrl = @"https://raw.githubusercontent.com/github/rest-api-description/aef5e31a2d10fdaab311ec6d18a453021a81383d/descriptions/ghes-3.16/ghes-3.16.2022-11-28.yaml" ;
69- private const string GHESJsonDescriptionUrl = @"https://raw.githubusercontent.com/github/rest-api-description/aef5e31a2d10fdaab311ec6d18a453021a81383d/descriptions/ghes-3.16/ghes-3.16.2022-11-28.json" ;
70- 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" ;
71- 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" ;
72- private async Task < OpenApiDocument > ParseDocumentAsync ( string fileName , string format = null )
69+
70+ private OpenApiReaderSettings _readerSettings ;
71+
72+ private const string PetStoreYamlResourceName = "petStore.yaml" ;
73+ private const string PetStoreJsonResourceName = "petStore.json" ;
74+
75+ private const string GHESRepoCommitSha = "aef5e31a2d10fdaab311ec6d18a453021a81383d" ;
76+ private const string GHESReleaseVersion = "ghes-3.16" ;
77+ private const string GHESDescriptionFileName = "ghes-3.16.2022-11-28" ;
78+
79+ // Building the four GHES URLs from shared constants means the pinned commit only needs to
80+ // change in one place when the benchmark data set is refreshed, instead of four near-identical
81+ // literals that can silently drift out of sync with each other.
82+ private static string BuildGHESDescriptionUrl ( string descriptionsFolder , string extension ) =>
83+ $ "https://raw.githubusercontent.com/github/rest-api-description/{ GHESRepoCommitSha } /{ descriptionsFolder } /{ GHESReleaseVersion } /{ GHESDescriptionFileName } .{ extension } ";
84+
85+ private static readonly string GHESYamlDescriptionUrl = BuildGHESDescriptionUrl ( "descriptions" , "yaml" ) ;
86+ private static readonly string GHESJsonDescriptionUrl = BuildGHESDescriptionUrl ( "descriptions" , "json" ) ;
87+ private static readonly string GHESNextYamlDescriptionUrl = BuildGHESDescriptionUrl ( "descriptions-next" , "yaml" ) ;
88+ private static readonly string GHESNextJsonDescriptionUrl = BuildGHESDescriptionUrl ( "descriptions-next" , "json" ) ;
89+
90+ private async Task < OpenApiDocument > ParseDocumentAsync ( DescriptionSource source , string format = null )
7391 {
7492 format ??= OpenApiConstants . Yaml ;
75- var stream = _streams [ fileName ] ;
93+ var stream = _streams [ source ] ;
7694 stream . Seek ( 0 , SeekOrigin . Begin ) ;
77-
78- var ( document , _) = await OpenApiDocument . LoadAsync ( stream , format , readerSettings ) . ConfigureAwait ( false ) ;
95+
96+ var ( document , _) = await OpenApiDocument . LoadAsync ( stream , format , _readerSettings ) . ConfigureAwait ( false ) ;
7997 return document ;
8098 }
99+
81100 private HttpClient _httpClient ;
82- private async Task LoadDocumentFromUrlIntoStreams ( string url )
101+
102+ private async Task < ( DescriptionSource Source , MemoryStream Stream ) > LoadFromUrlAsync ( DescriptionSource source , string url )
83103 {
84- var response = await _httpClient . GetAsync ( url ) . ConfigureAwait ( false ) ;
104+ using var response = await _httpClient . GetAsync ( url ) . ConfigureAwait ( false ) ;
85105 response . EnsureSuccessStatusCode ( ) ;
86- var stream = new MemoryStream ( ) ; // NOT disposed on purpose
106+
107+ var stream = new MemoryStream ( ) ;
87108 await response . Content . CopyToAsync ( stream ) . ConfigureAwait ( false ) ;
88109 stream . Seek ( 0 , SeekOrigin . Begin ) ;
89- _streams . Add ( url , stream ) ;
110+ return ( source , stream ) ;
90111 }
91- private static readonly Assembly assembly = typeof ( Descriptions ) . GetTypeInfo ( ) . Assembly ;
92- private async Task LoadDocumentFromAssemblyIntoStreams ( string fileName )
112+
113+ private static readonly Assembly _assembly = typeof ( Descriptions ) . Assembly ;
114+
115+ private async Task < ( DescriptionSource Source , MemoryStream Stream ) > LoadFromAssemblyAsync ( DescriptionSource source , string resourceFileName )
93116 {
94- using var resource = assembly . GetManifestResourceStream ( $ "PerformanceTests.{ fileName } ") ;
95- var stream = new MemoryStream ( ) ; // NOT disposed on purpose
96- await resource . CopyToAsync ( stream ) . ConfigureAwait ( false ) ;
117+ using var resourceStream = _assembly . GetManifestResourceStream ( $ "PerformanceTests.{ resourceFileName } ") ;
118+ var stream = new MemoryStream ( ) ;
119+ await resourceStream . CopyToAsync ( stream ) . ConfigureAwait ( false ) ;
97120 stream . Seek ( 0 , SeekOrigin . Begin ) ;
98- _streams . Add ( fileName , stream ) ;
121+ return ( source , stream ) ;
99122 }
123+
100124 [ GlobalCleanup ]
101125 public void Cleanup ( )
102126 {
0 commit comments