Skip to content

Commit cd1bae9

Browse files
authored
[Perf] Add support for test-proxy (#22621)
1 parent 2193770 commit cd1bae9

File tree

13 files changed

+259
-105
lines changed

13 files changed

+259
-105
lines changed

common/Perf/Azure.Test.Perf/IPerfTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ internal interface IPerfTest : IDisposable, IAsyncDisposable
1111
{
1212
Task GlobalSetupAsync();
1313
Task SetupAsync();
14+
Task RecordAndStartPlayback();
1415
void Run(CancellationToken cancellationToken);
1516
Task RunAsync(CancellationToken cancellationToken);
17+
Task StopPlayback();
1618
Task CleanupAsync();
1719
Task GlobalCleanupAsync();
1820
}

common/Perf/Azure.Test.Perf/PerfOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using CommandLine;
5+
using System;
56

67
namespace Azure.Test.Perf
78
{
@@ -55,6 +56,9 @@ public class PerfOptions
5556
[Option("sync", HelpText = "Runs sync version of test")]
5657
public bool Sync { get; set; }
5758

59+
[Option('x', "test-proxy", HelpText = "URI of TestProxy Server")]
60+
public Uri TestProxy { get; set; }
61+
5862
[Option('w', "warmup", Default = 5, HelpText = "Duration of warmup in seconds")]
5963
public int Warmup { get; set; }
6064
}

common/Perf/Azure.Test.Perf/PerfProgram.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,26 @@ private static async Task Run(Type testType, PerfOptions options)
105105
{
106106
await tests[0].GlobalSetupAsync();
107107

108+
var startedPlayback = false;
109+
108110
try
109111
{
110112
await Task.WhenAll(tests.Select(t => t.SetupAsync()));
111113
setupStatusCts.Cancel();
112114
setupStatusThread.Join();
113115

116+
if (options.TestProxy != null)
117+
{
118+
using var recordStatusCts = new CancellationTokenSource();
119+
var recordStatusThread = PerfStressUtilities.PrintStatus("=== Record and Start Playback ===", () => ".", newLine: false, recordStatusCts.Token);
120+
121+
await Task.WhenAll(tests.Select(t => t.RecordAndStartPlayback()));
122+
startedPlayback = true;
123+
124+
recordStatusCts.Cancel();
125+
recordStatusThread.Join();
126+
}
127+
114128
if (options.Warmup > 0)
115129
{
116130
await RunTestsAsync(tests, options, "Warmup", warmup: true);
@@ -141,14 +155,28 @@ private static async Task Run(Type testType, PerfOptions options)
141155
}
142156
finally
143157
{
144-
if (!options.NoCleanup)
158+
try
145159
{
146-
if (cleanupStatusThread == null)
160+
if (startedPlayback)
147161
{
148-
cleanupStatusThread = PerfStressUtilities.PrintStatus("=== Cleanup ===", () => ".", newLine: false, cleanupStatusCts.Token);
162+
using var playbackStatusCts = new CancellationTokenSource();
163+
var playbackStatusThread = PerfStressUtilities.PrintStatus("=== Stop Playback ===", () => ".", newLine: false, playbackStatusCts.Token);
164+
await Task.WhenAll(tests.Select(t => t.StopPlayback()));
165+
playbackStatusCts.Cancel();
166+
playbackStatusThread.Join();
149167
}
168+
}
169+
finally
170+
{
171+
if (!options.NoCleanup)
172+
{
173+
if (cleanupStatusThread == null)
174+
{
175+
cleanupStatusThread = PerfStressUtilities.PrintStatus("=== Cleanup ===", () => ".", newLine: false, cleanupStatusCts.Token);
176+
}
150177

151-
await Task.WhenAll(tests.Select(t => t.CleanupAsync()));
178+
await Task.WhenAll(tests.Select(t => t.CleanupAsync()));
179+
}
152180
}
153181
}
154182
}

common/Perf/Azure.Test.Perf/PerfTest.cs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,87 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using Azure.Core;
5+
using Azure.Core.Pipeline;
46
using System;
7+
using System.Linq;
8+
using System.Net;
9+
using System.Net.Http;
510
using System.Threading;
611
using System.Threading.Tasks;
712

813
namespace Azure.Test.Perf
914
{
1015
public abstract class PerfTest<TOptions> : IPerfTest where TOptions : PerfOptions
1116
{
17+
private readonly HttpPipelineTransport _insecureTransport;
18+
19+
private readonly HttpClient _recordPlaybackHttpClient;
20+
private readonly TestProxyPolicy _testProxyPolicy;
21+
22+
private string _recordingId;
23+
1224
protected TOptions Options { get; private set; }
1325

1426
public PerfTest(TOptions options)
1527
{
1628
Options = options;
29+
30+
if (Options.Insecure)
31+
{
32+
var transport = (new PerfClientOptions()).Transport;
33+
34+
// Disable SSL validation
35+
if (transport is HttpClientTransport)
36+
{
37+
_insecureTransport = new HttpClientTransport(new HttpClient(new HttpClientHandler()
38+
{
39+
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
40+
}));
41+
}
42+
else
43+
{
44+
// Assume _transport is HttpWebRequestTransport (currently internal class)
45+
ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true;
46+
47+
_insecureTransport = transport;
48+
}
49+
}
50+
51+
if (Options.TestProxy != null)
52+
{
53+
if (Options.Insecure)
54+
{
55+
_recordPlaybackHttpClient = new HttpClient(new HttpClientHandler()
56+
{
57+
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
58+
});
59+
}
60+
else
61+
{
62+
_recordPlaybackHttpClient = new HttpClient();
63+
}
64+
65+
_testProxyPolicy = new TestProxyPolicy(Options.TestProxy);
66+
}
67+
}
68+
69+
protected TClientOptions ConfigureClientOptions<TClientOptions>(TClientOptions clientOptions) where TClientOptions : ClientOptions
70+
{
71+
if (_insecureTransport != null)
72+
{
73+
clientOptions.Transport = _insecureTransport;
74+
}
75+
76+
if (_testProxyPolicy != null)
77+
{
78+
// TestProxyPolicy should be per-retry to run as late as possible in the pipeline. For example, some
79+
// clients compute a request signature as a per-retry policy, and TestProxyPolicy should run after the
80+
// signature is computed to avoid altering the signature.
81+
clientOptions.AddPolicy(_testProxyPolicy, HttpPipelinePosition.PerRetry);
82+
}
83+
84+
return clientOptions;
1785
}
1886

1987
public virtual Task GlobalSetupAsync()
@@ -26,10 +94,48 @@ public virtual Task SetupAsync()
2694
return Task.CompletedTask;
2795
}
2896

97+
public async Task RecordAndStartPlayback()
98+
{
99+
await StartRecording();
100+
101+
_testProxyPolicy.RecordingId = _recordingId;
102+
_testProxyPolicy.Mode = "record";
103+
104+
// Record one call to Run()
105+
if (Options.Sync)
106+
{
107+
Run(CancellationToken.None);
108+
}
109+
else
110+
{
111+
await RunAsync(CancellationToken.None);
112+
}
113+
114+
await StopRecording();
115+
116+
await StartPlayback();
117+
118+
_testProxyPolicy.Mode = "playback";
119+
_testProxyPolicy.RecordingId = _recordingId;
120+
}
121+
29122
public abstract void Run(CancellationToken cancellationToken);
30123

31124
public abstract Task RunAsync(CancellationToken cancellationToken);
32125

126+
public async Task StopPlayback()
127+
{
128+
var message = new HttpRequestMessage(HttpMethod.Post, new Uri(Options.TestProxy, "/playback/stop"));
129+
message.Headers.Add("x-recording-id", _recordingId);
130+
message.Headers.Add("x-purge-inmemory-recording", bool.TrueString);
131+
132+
await _recordPlaybackHttpClient.SendAsync(message);
133+
134+
// Stop redirecting requests to test proxy
135+
_testProxyPolicy.Mode = null;
136+
_testProxyPolicy.RecordingId = null;
137+
}
138+
33139
public virtual Task CleanupAsync()
34140
{
35141
return Task.CompletedTask;
@@ -73,5 +179,33 @@ protected static string GetEnvironmentVariable(string name)
73179
}
74180
return value;
75181
}
182+
183+
private async Task StartRecording()
184+
{
185+
var message = new HttpRequestMessage(HttpMethod.Post, new Uri(Options.TestProxy, "/record/start"));
186+
187+
var response = await _recordPlaybackHttpClient.SendAsync(message);
188+
_recordingId = response.Headers.GetValues("x-recording-id").Single();
189+
}
190+
191+
private async Task StopRecording()
192+
{
193+
var message = new HttpRequestMessage(HttpMethod.Post, new Uri(Options.TestProxy, "/record/stop"));
194+
message.Headers.Add("x-recording-id", _recordingId);
195+
196+
await _recordPlaybackHttpClient.SendAsync(message);
197+
}
198+
199+
private async Task StartPlayback()
200+
{
201+
var message = new HttpRequestMessage(HttpMethod.Post, new Uri(Options.TestProxy, "/playback/start"));
202+
message.Headers.Add("x-recording-id", _recordingId);
203+
204+
var response = await _recordPlaybackHttpClient.SendAsync(message);
205+
_recordingId = response.Headers.GetValues("x-recording-id").Single();
206+
}
207+
208+
// Dummy class used to fetch default HttpPipelineTransport
209+
private class PerfClientOptions : ClientOptions { }
76210
}
77211
}

common/Perf/Azure.Test.Perf/PerfTransport.cs

Lines changed: 0 additions & 86 deletions
This file was deleted.

0 commit comments

Comments
 (0)