Skip to content

Commit 0ff2bb6

Browse files
[Perf] Support for test proxy and profiling (#19338)
* Added profiler support to perf framework * Removed old loop kwarg * Fixed list test bug * Support old API version * Updated perf framework for test proxy * Support test proxy * Whitespace * Support proxy in legacy tests * Update perf test guide * Support proxy in GetSecretTest Co-authored-by: Mike Harder <[email protected]>
1 parent ff778a6 commit 0ff2bb6

File tree

13 files changed

+280
-50
lines changed

13 files changed

+280
-50
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ build/
3535
TestResults/
3636
ENV_DIR/
3737

38+
# Perf test profiling
39+
cProfile-*.pstats
40+
3841
# tox generated artifacts
3942
test-junit-*.xml
4043
pylint-*.out.txt

doc/dev/perfstress_tests.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
1. [The perfstress framework](#the-perfstress-framework)
33
- [The PerfStressTest base](#the-perfstresstest-base)
44
- [Default command options](#default-command-options)
5+
- [Running with test proxy](#running-with-test-proxy)
56
2. [Adding performance tests to an SDK](#adding-performance-tests-to-an-sdk)
67
- [Writing a test](#writing-a-test)
78
- [Adding legacy T1 tests](#adding-legacy-t1-tests)
@@ -38,6 +39,16 @@ class PerfStressTest:
3839
async def global_cleanup(self):
3940
# Can be optionally defined. Only run once, regardless of parallelism.
4041

42+
async def record_and_start_playback(self):
43+
# Set up the recording on the test proxy, and configure the proxy in playback mode.
44+
# This function is only run if a test proxy URL is provided (-x).
45+
# There should be no need to overwrite this function.
46+
47+
async def stop_playback(self):
48+
# Configure the proxy out of playback mode and discard the recording.
49+
# This function is only run if a test proxy URL is provided (-x).
50+
# There should be no need to overwrite this function.
51+
4152
async def setup(self):
4253
# Can be optionally defined. Run once per test instance, after global_setup.
4354

@@ -65,12 +76,24 @@ class PerfStressTest:
6576
```
6677
## Default command options
6778
The framework has a series of common command line options built in:
68-
- `--duration=10` Number of seconds to run as many operations (the "run" function) as possible. Default is 10.
69-
- `--iterations=1` Number of test iterations to run. Default is 1.
70-
- `--parallel=1` Number of tests to run in parallel. Default is 1.
71-
- `--warm-up=5` Number of seconds to spend warming up the connection before measuring begins. Default is 5.
79+
- `-d --duration=10` Number of seconds to run as many operations (the "run" function) as possible. Default is 10.
80+
- `-i --iterations=1` Number of test iterations to run. Default is 1.
81+
- `-p --parallel=1` Number of tests to run in parallel. Default is 1.
82+
- `-w --warm-up=5` Number of seconds to spend warming up the connection before measuring begins. Default is 5.
7283
- `--sync` Whether to run the tests in sync or async. Default is False (async).
7384
- `--no-cleanup` Whether to keep newly created resources after test run. Default is False (resources will be deleted).
85+
- `-x --test-proxy` Whether to run the tests against the test proxy server. Specfiy the URL for the proxy endpoint (e.g. "https://localhost:5001").
86+
- `--profile` Whether to run the perftest with cProfile. If enabled (default is False), the output file of the **last completed single iteration** will be written to the current working directory in the format `"cProfile-<TestClassName>-<TestID>-<sync/async>.pstats"`.
87+
88+
89+
## Running with the test proxy
90+
Follow the instructions here to install and run the test proxy server:
91+
https://github.com/Azure/azure-sdk-tools/tree/feature/http-recording-server/tools/test-proxy/Azure.Sdk.Tools.TestProxy
92+
93+
Once running, in a separate process run the perf test in question, combined with the `-x` flag to specify the proxy endpoint.
94+
```cmd
95+
(env) ~/azure-storage-blob/tests> perfstress DownloadTest -x "https://localhost:5001"
96+
```
7497

7598
# Adding performance tests to an SDK
7699
The performance tests will be in a submodule called `perfstress_tests` within the `tests` directory in an SDK project.
@@ -351,5 +374,5 @@ Using the `perfstress` command alone will list the available perf tests found. N
351374

352375
Please add a `README.md` to the perfstress_tests directory so that others know how to setup and run the perf tests, along with a description of the available tests and any support command line options. README files in a `tests/perfstress_tests` directory should already be filtered from CI validation for SDK readmes.
353376
Some examples can be found here:
354-
- [Azure Storage Blob](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/storage/azure-storage-blob/tests/perfstress_tests/README.md)
355-
- [Azure Service Bus](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/servicebus/azure-servicebus/tests/perf_tests/README.md)
377+
- [Azure Storage Blob](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/storage/azure-storage-blob/tests/perfstress_tests/README.md)
378+
- [Azure Service Bus](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/servicebus/azure-servicebus/tests/perf_tests/README.md)

sdk/keyvault/azure-keyvault-secrets/tests/perfstress_tests/get_secret.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def __init__(self, arguments):
2121

2222
# Create clients
2323
vault_url = self.get_from_env("AZURE_KEYVAULT_URL")
24-
self.client = SecretClient(vault_url, self.credential)
25-
self.async_client = AsyncSecretClient(vault_url, self.async_credential)
24+
self.client = SecretClient(vault_url, self.credential, **self._client_kwargs)
25+
self.async_client = AsyncSecretClient(vault_url, self.async_credential, **self._client_kwargs)
2626

2727
async def global_setup(self):
2828
"""The global setup is run only once."""

sdk/storage/azure-storage-blob/azure/storage/blob/aio/_blob_client_async.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ def __init__(
122122
self._client = AzureBlobStorage(url=self.url, pipeline=self._pipeline)
123123
default_api_version = self._client._config.version # pylint: disable=protected-access
124124
self._client._config.version = get_api_version(kwargs, default_api_version) # pylint: disable=protected-access
125-
self._loop = kwargs.get('loop', None)
126125

127126
@distributed_trace_async
128127
async def get_account_information(self, **kwargs): # type: ignore

sdk/storage/azure-storage-blob/azure/storage/blob/aio/_blob_service_client_async.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ def __init__(
119119
self._client = AzureBlobStorage(url=self.url, pipeline=self._pipeline)
120120
default_api_version = self._client._config.version # pylint: disable=protected-access
121121
self._client._config.version = get_api_version(kwargs, default_api_version) # pylint: disable=protected-access
122-
self._loop = kwargs.get('loop', None)
123122

124123
@distributed_trace_async
125124
async def get_user_delegation_key(self, key_start_time, # type: datetime
@@ -620,7 +619,7 @@ def get_container_client(self, container):
620619
credential=self.credential, api_version=self.api_version, _configuration=self._config,
621620
_pipeline=_pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
622621
require_encryption=self.require_encryption, key_encryption_key=self.key_encryption_key,
623-
key_resolver_function=self.key_resolver_function, loop=self._loop)
622+
key_resolver_function=self.key_resolver_function)
624623

625624
def get_blob_client(
626625
self, container, # type: Union[ContainerProperties, str]
@@ -675,4 +674,4 @@ def get_blob_client(
675674
credential=self.credential, api_version=self.api_version, _configuration=self._config,
676675
_pipeline=_pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
677676
require_encryption=self.require_encryption, key_encryption_key=self.key_encryption_key,
678-
key_resolver_function=self.key_resolver_function, loop=self._loop)
677+
key_resolver_function=self.key_resolver_function)

sdk/storage/azure-storage-blob/azure/storage/blob/aio/_container_client_async.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ def __init__(
119119
self._client = AzureBlobStorage(url=self.url, pipeline=self._pipeline)
120120
default_api_version = self._client._config.version # pylint: disable=protected-access
121121
self._client._config.version = get_api_version(kwargs, default_api_version) # pylint: disable=protected-access
122-
self._loop = kwargs.get('loop', None)
123122

124123
@distributed_trace_async
125124
async def create_container(self, metadata=None, public_access=None, **kwargs):
@@ -1207,4 +1206,4 @@ def get_blob_client(
12071206
credential=self.credential, api_version=self.api_version, _configuration=self._config,
12081207
_pipeline=_pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
12091208
require_encryption=self.require_encryption, key_encryption_key=self.key_encryption_key,
1210-
key_resolver_function=self.key_resolver_function, loop=self._loop)
1209+
key_resolver_function=self.key_resolver_function)

sdk/storage/azure-storage-blob/tests/perfstress_tests/README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ Using the `perfstress` command alone will list the available perf tests found. N
3737

3838
### Common perf command line options
3939
These options are available for all perf tests:
40-
- `--duration=10` Number of seconds to run as many operations (the "run" function) as possible. Default is 10.
41-
- `--iterations=1` Number of test iterations to run. Default is 1.
42-
- `--parallel=1` Number of tests to run in parallel. Default is 1.
40+
- `-d --duration=10` Number of seconds to run as many operations (the "run" function) as possible. Default is 10.
41+
- `-i --iterations=1` Number of test iterations to run. Default is 1.
42+
- `-p --parallel=1` Number of tests to run in parallel. Default is 1.
4343
- `--no-client-share` Whether each parallel test instance should share a single client, or use their own. Default is False (sharing).
44-
- `--warm-up=5` Number of seconds to spend warming up the connection before measuring begins. Default is 5.
44+
- `-w --warm-up=5` Number of seconds to spend warming up the connection before measuring begins. Default is 5.
4545
- `--sync` Whether to run the tests in sync or async. Default is False (async). This flag must be used for Storage legacy tests, which do not support async.
4646
- `--no-cleanup` Whether to keep newly created resources after test run. Default is False (resources will be deleted).
47+
- `-x --test-proxy` Whether to run the tests against the test proxy server. Specfiy the URL for the proxy endpoint (e.g. "https://localhost:5001"). WARNING: When using with Legacy tests - only HTTPS is supported.
48+
- `--profile` Whether to run the perftest with cProfile. If enabled (default is False), the output file of the **last completed single iteration** will be written to the current working directory in the format `"cProfile-<TestClassName>-<TestID>-<sync/async>.pstats"`.
4749

4850
### Common Blob command line options
4951
The options are available for all Blob perf tests:
@@ -77,3 +79,12 @@ The tests currently written for the T1 SDK:
7779
```cmd
7880
(env) ~/azure-storage-blob/tests> perfstress UploadTest --parallel=2 --size=10240
7981
```
82+
83+
## Running with the test proxy
84+
Follow the instructions here to install and run the test proxy server:
85+
https://github.com/Azure/azure-sdk-tools/tree/feature/http-recording-server/tools/test-proxy/Azure.Sdk.Tools.TestProxy
86+
87+
Once running, in a separate process run the perf test in question, combined with the `-x` flag to specify the proxy endpoint. (Note, only the HTTPS endpoint is supported for the Legacy tests).
88+
```cmd
89+
(env) ~/azure-storage-blob/tests> perfstress DownloadTest -x "https://localhost:5001"
90+
```

sdk/storage/azure-storage-blob/tests/perfstress_tests/T1_legacy_tests/_test_base_legacy.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,57 @@
55

66
import os
77
import uuid
8+
import functools
9+
10+
import requests
811

912
from azure_devtools.perfstress_tests import PerfStressTest
1013

1114
from azure.storage.blob import BlockBlobService
1215

16+
17+
def test_proxy_callback(proxy_policy, request):
18+
if proxy_policy.recording_id and proxy_policy.mode:
19+
live_endpoint = request.host
20+
request.host = proxy_policy._proxy_url.netloc
21+
request.headers["x-recording-id"] = proxy_policy.recording_id
22+
request.headers["x-recording-mode"] = proxy_policy.mode
23+
request.headers["x-recording-remove"] = "false"
24+
25+
# Ensure x-recording-upstream-base-uri header is only set once, since the
26+
# same HttpMessage will be reused on retries
27+
if "x-recording-upstream-base-uri" not in request.headers:
28+
original_endpoint = "https://{}".format(live_endpoint)
29+
request.headers["x-recording-upstream-base-uri"] = original_endpoint
30+
31+
1332
class _LegacyServiceTest(PerfStressTest):
1433
service_client = None
1534
async_service_client = None
1635

1736
def __init__(self, arguments):
1837
super().__init__(arguments)
1938
connection_string = self.get_from_env("AZURE_STORAGE_CONNECTION_STRING")
39+
session = None
40+
if self.args.test_proxy:
41+
session = requests.Session()
42+
session.verify = False
2043
if not _LegacyServiceTest.service_client or self.args.no_client_share:
21-
_LegacyServiceTest.service_client = BlockBlobService(connection_string=connection_string)
44+
_LegacyServiceTest.service_client = BlockBlobService(
45+
connection_string=connection_string,
46+
request_session=session)
2247
_LegacyServiceTest.service_client.MAX_SINGLE_PUT_SIZE = self.args.max_put_size
2348
_LegacyServiceTest.service_client.MAX_BLOCK_SIZE = self.args.max_block_size
2449
_LegacyServiceTest.service_client.MIN_LARGE_BLOCK_UPLOAD_THRESHOLD = self.args.buffer_threshold
2550
self.async_service_client = None
2651
self.service_client = _LegacyServiceTest.service_client
2752

53+
if self.args.test_proxy:
54+
self.service_client.request_callback = functools.partial(
55+
test_proxy_callback,
56+
self._test_proxy_policy
57+
)
58+
2859
@staticmethod
2960
def add_arguments(parser):
3061
super(_LegacyServiceTest, _LegacyServiceTest).add_arguments(parser)

sdk/storage/azure-storage-blob/tests/perfstress_tests/_test_base.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ class _ServiceTest(PerfStressTest):
2020
def __init__(self, arguments):
2121
super().__init__(arguments)
2222
connection_string = self.get_from_env("AZURE_STORAGE_CONNECTION_STRING")
23-
kwargs = {}
24-
kwargs['max_single_put_size'] = self.args.max_put_size
25-
kwargs['max_block_size'] = self.args.max_block_size
26-
kwargs['min_large_block_upload_threshold'] = self.args.buffer_threshold
23+
if self.args.test_proxy:
24+
self._client_kwargs['_additional_pipeline_policies'] = self._client_kwargs['per_retry_policies']
25+
self._client_kwargs['max_single_put_size'] = self.args.max_put_size
26+
self._client_kwargs['max_block_size'] = self.args.max_block_size
27+
self._client_kwargs['min_large_block_upload_threshold'] = self.args.buffer_threshold
28+
# self._client_kwargs['api_version'] = '2019-02-02' # Used only for comparison with T1 legacy tests
29+
2730
if not _ServiceTest.service_client or self.args.no_client_share:
28-
_ServiceTest.service_client = SyncBlobServiceClient.from_connection_string(conn_str=connection_string, **kwargs)
29-
_ServiceTest.async_service_client = AsyncBlobServiceClient.from_connection_string(conn_str=connection_string, **kwargs)
31+
_ServiceTest.service_client = SyncBlobServiceClient.from_connection_string(conn_str=connection_string, **self._client_kwargs)
32+
_ServiceTest.async_service_client = AsyncBlobServiceClient.from_connection_string(conn_str=connection_string, **self._client_kwargs)
3033
self.service_client = _ServiceTest.service_client
3134
self.async_service_client =_ServiceTest.async_service_client
3235

sdk/storage/azure-storage-blob/tests/perfstress_tests/list_blobs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ async def global_setup(self):
2222
next_upload = next(pending)
2323
running.add(next_upload)
2424
except StopIteration:
25-
await asyncio.wait(running, return_when=asyncio.ALL_COMPLETED)
25+
if running:
26+
await asyncio.wait(running, return_when=asyncio.ALL_COMPLETED)
2627
break
2728

2829
def run_sync(self):

0 commit comments

Comments
 (0)