diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/README.md b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/README.md new file mode 100644 index 000000000000..16a0730bfaaa --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/README.md @@ -0,0 +1,71 @@ +# FileShare Performance Tests + +In order to run the performance tests, the `azure-devtools` package must be installed. This is done as part of the `dev_requirements`. +Start be creating a new virtual environment for your perf tests. This will need to be a Python 3 environment, preferably >=3.7. +Note that tests for T1 and T2 SDKs cannot be run from the same environment, and will need to be setup separately. + +### Setup for test resources + +These tests will run against a pre-configured Storage account. The following environment variable will need to be set for the tests to access the live resources: +``` +AZURE_STORAGE_CONNECTION_STRING= +``` + +### Setup for T2 perf test runs + +```cmd +(env) ~/azure-storage-file-share> pip install -r dev_requirements.txt +(env) ~/azure-storage-file-share> pip install -e . +``` + +### Setup for T1 perf test runs + +```cmd +(env) ~/azure-storage-file-share> pip install -r dev_requirements.txt +(env) ~/azure-storage-file-share> pip install tests/perfstress_tests/T1_legacy_tests/t1_test_requirements.txt +``` + +## Test commands + +When `azure-devtools` is installed, you will have access to the `perfstress` command line tool, which will scan the current module for runable perf tests. Only a specific test can be run at a time (i.e. there is no "run all" feature). + +```cmd +(env) ~/azure-storage-file-share> cd tests +(env) ~/azure-storage-file-share/tests> perfstress +``` +Using the `perfstress` command alone will list the available perf tests found. Note that the available tests discovered will vary depending on whether your environment is configured for the T1 or T2 SDK. + +### Common perf command line options +These options are available for all perf tests: +- `--duration=10` Number of seconds to run as many operations (the "run" function) as possible. Default is 10. +- `--iterations=1` Number of test iterations to run. Default is 1. +- `--parallel=1` Number of tests to run in parallel. Default is 1. +- `--no-client-share` Whether each parallel test instance should share a single client, or use their own. Default is False (sharing). +- `--warm-up=5` Number of seconds to spend warming up the connection before measuring begins. Default is 5. +- `--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. +- `--no-cleanup` Whether to keep newly created resources after test run. Default is False (resources will be deleted). + +### Common FileShare command line options +The options are available for all SB perf tests: +- `--size=100` Size in bytes of data to be transferred in upload or download tests. Default is 10240. +- `--max-concurrency=1` Number of threads to concurrently upload/download a single operation using the SDK API parameter. Default is 1. +- `--max-range-size`Maximum size of data uploading in single HTTP PUT. Default is 4*1024*1024. + +### T2 Tests +The tests currently written for the T2 SDK: +- `UploadTest` Uploads a stream of `size` bytes to a new File. +- `UploadFromFileTest` Uploads a local file of `size` bytes to a new File. +- `DownloadTest` Download a stream of `size` bytes. +- `DownloadToFileTest` Download `size` bytes into a local file. + +### T1 Tests +The tests currently written for the T2 SDK: +- `LegacyUploadTest` Uploads a stream of `size` bytes to a new File. +- `LegacyUploadFromFileTest` Uploads a local file of `size` bytes to a new File. +- `LegacyDownloadTest` Download a stream of `size` bytes. +- `LegacyDownloadToFileTest` Download `size` bytes into a local file. + +## Example command +```cmd +(env) ~/azure-storage-file-share/tests> perfstress UploadTest --parallel=2 --size=10240 +``` \ No newline at end of file diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/__init__.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/_test_base.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/_test_base.py new file mode 100644 index 000000000000..7ff978632e49 --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/_test_base.py @@ -0,0 +1,48 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import uuid + +from azure_devtools.perfstress_tests import PerfStressTest + +from azure.storage.file import FileService + +class _LegacyServiceTest(PerfStressTest): + service_client = None + async_service_client = None + + def __init__(self, arguments): + super().__init__(arguments) + connection_string = self.get_from_env("AZURE_STORAGE_CONNECTION_STRING") + if not _LegacyServiceTest.service_client or self.args.no_client_share: + _LegacyServiceTest.service_client = FileService(connection_string=connection_string) + if self.args.max_range_size: + _LegacyServiceTest.service_client.MAX_RANGE_SIZE = self.args.max_range_size + self.async_service_client = None + self.service_client = _LegacyServiceTest.service_client + + @staticmethod + def add_arguments(parser): + super(_LegacyServiceTest, _LegacyServiceTest).add_arguments(parser) + parser.add_argument('-r', '--max-range-size', nargs='?', type=int, help='Maximum size of data uploading in single HTTP PUT. Defaults to 4*1024*1024', default=4*1024*1024) + parser.add_argument('-c', '--max-concurrency', nargs='?', type=int, help='Maximum number of concurrent threads used for data transfer. Defaults to 1', default=1) + parser.add_argument('-s', '--size', nargs='?', type=int, help='Size of data to transfer. Default is 10240.', default=10240) + parser.add_argument('--no-client-share', action='store_true', help='Create one ServiceClient per test instance. Default is to share a single ServiceClient.', default=False) + + +class _LegacyShareTest(_LegacyServiceTest): + share_name = "perfstress-legacy-" + str(uuid.uuid4()) + + def __init__(self, arguments): + super().__init__(arguments) + + async def global_setup(self): + await super().global_setup() + self.service_client.create_share(self.share_name) + + async def global_cleanup(self): + self.service_client.delete_share(self.share_name) + await super().global_cleanup() diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/download.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/download.py new file mode 100644 index 000000000000..662d21f92cf3 --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/download.py @@ -0,0 +1,36 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azure_devtools.perfstress_tests import get_random_bytes, WriteStream + +from ._test_base import _LegacyShareTest + + +class LegacyDownloadTest(_LegacyShareTest): + def __init__(self, arguments): + super().__init__(arguments) + self.file_name = "downloadtest" + self.download_stream = WriteStream() + + async def global_setup(self): + await super().global_setup() + data = get_random_bytes(self.args.size) + self.service_client.create_file_from_bytes( + share_name=self.share_name, + directory_name=None, + file_name=self.file_name, + file=data) + + def run_sync(self): + self.download_stream.reset() + self.service_client.get_file_to_stream( + share_name=self.share_name, + directory_name=None, + file_name=self.file_name, + stream=self.download_stream, + max_connections=self.args.max_concurrency) + + async def run_async(self): + raise NotImplementedError("Async not supported for legacy T1 tests.") diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/download_to_file.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/download_to_file.py new file mode 100644 index 000000000000..ff5153c1268c --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/download_to_file.py @@ -0,0 +1,44 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import tempfile +import uuid + +from azure_devtools.perfstress_tests import get_random_bytes + +from ._test_base import _LegacyShareTest + + +class LegacyDownloadToFileTest(_LegacyShareTest): + file_name = "downloadtest" + + async def global_setup(self): + await super().global_setup() + data = get_random_bytes(self.args.size) + self.service_client.create_file_from_bytes( + share_name=self.share_name, + directory_name=None, + file_name=self.file_name, + file=data) + + async def setup(self): + await super().setup() + self.temp_file = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())) + + async def cleanup(self): + os.remove(self.temp_file) + await super().cleanup() + + def run_sync(self): + self.service_client.get_file_to_path( + share_name=self.share_name, + directory_name=None, + file_name=self.file_name, + file_path=self.temp_file, + max_connections=self.args.max_concurrency) + + async def run_async(self): + raise NotImplementedError("Async not supported for legacy T1 tests.") diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/t1_test_requirements.txt b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/t1_test_requirements.txt new file mode 100644 index 000000000000..f162f7a0e8df --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/t1_test_requirements.txt @@ -0,0 +1 @@ +azure-storage-file==2.1.0 diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/upload.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/upload.py new file mode 100644 index 000000000000..8e59e3caeb2e --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/upload.py @@ -0,0 +1,30 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import uuid + +from azure_devtools.perfstress_tests import RandomStream, get_random_bytes + +from ._test_base import _LegacyShareTest + + +class LegacyUploadTest(_LegacyShareTest): + def __init__(self, arguments): + super().__init__(arguments) + self.file_name = "sharefiletest-" + str(uuid.uuid4()) + self.upload_stream = RandomStream(self.args.size) + + def run_sync(self): + self.upload_stream.reset() + self.service_client.create_file_from_stream( + share_name=self.share_name, + directory_name=None, + file_name=self.file_name, + stream=self.upload_stream, + count=self.args.size, + max_connections=self.args.max_concurrency) + + async def run_async(self): + raise NotImplementedError("Async not supported for legacy T1 tests.") diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/upload_from_file.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/upload_from_file.py new file mode 100644 index 000000000000..d762c2cf4f4a --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/T1_legacy_tests/upload_from_file.py @@ -0,0 +1,42 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import tempfile +import uuid + +from azure_devtools.perfstress_tests import get_random_bytes + +from ._test_base import _LegacyShareTest + + +class LegacyUploadFromFileTest(_LegacyShareTest): + temp_file = None + + def __init__(self, arguments): + super().__init__(arguments) + self.file_name = "sharefiletest-" + str(uuid.uuid4()) + + async def global_setup(self): + await super().global_setup() + data = get_random_bytes(self.args.size) + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + LegacyUploadFromFileTest.temp_file = temp_file.name + temp_file.write(data) + + async def global_cleanup(self): + os.remove(LegacyUploadFromFileTest.temp_file) + await super().global_cleanup() + + def run_sync(self): + self.service_client.create_file_from_path( + share_name=self.share_name, + directory_name=None, + file_name=self.file_name, + local_file_path=LegacyUploadFromFileTest.temp_file, + max_connections=self.args.max_concurrency) + + async def run_async(self): + raise NotImplementedError("Async not supported for legacy T1 tests.") diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/__init__.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/_test_base.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/_test_base.py new file mode 100644 index 000000000000..970e7dcf5ff6 --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/_test_base.py @@ -0,0 +1,74 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import uuid + +from azure_devtools.perfstress_tests import PerfStressTest + +from azure.storage.fileshare import ShareServiceClient as SyncShareServiceClient +from azure.storage.fileshare.aio import ShareServiceClient as AsyncShareServiceClient + + +class _ServiceTest(PerfStressTest): + service_client = None + async_service_client = None + + def __init__(self, arguments): + super().__init__(arguments) + connection_string = self.get_from_env("AZURE_STORAGE_CONNECTION_STRING") + kwargs = {} + if self.args.max_range_size: + kwargs['max_range_size'] = self.args.max_range_size + if not _ServiceTest.service_client or self.args.no_client_share: + _ServiceTest.service_client = SyncShareServiceClient.from_connection_string(conn_str=connection_string, **kwargs) + _ServiceTest.async_service_client = AsyncShareServiceClient.from_connection_string(conn_str=connection_string, **kwargs) + self.service_client = _ServiceTest.service_client + self.async_service_client =_ServiceTest.async_service_client + + async def close(self): + await self.async_service_client.close() + await super().close() + + @staticmethod + def add_arguments(parser): + super(_ServiceTest, _ServiceTest).add_arguments(parser) + parser.add_argument('-r', '--max-range-size', nargs='?', type=int, help='Maximum size of data uploading in single HTTP PUT. Defaults to 4*1024*1024', default=4*1024*1024) + parser.add_argument('-c', '--max-concurrency', nargs='?', type=int, help='Maximum number of concurrent threads used for data transfer. Defaults to 1', default=1) + parser.add_argument('-s', '--size', nargs='?', type=int, help='Size of data to transfer. Default is 10240.', default=10240) + parser.add_argument('--no-client-share', action='store_true', help='Create one ServiceClient per test instance. Default is to share a single ServiceClient.', default=False) + + +class _ShareTest(_ServiceTest): + share_name = "perfstress-" + str(uuid.uuid4()) + + def __init__(self, arguments): + super().__init__(arguments) + self.share_client = self.service_client.get_share_client(self.share_name) + self.async_share_client = self.async_service_client.get_share_client(self.share_name) + + async def global_setup(self): + await super().global_setup() + self.share_client.create_share() + + async def global_cleanup(self): + self.share_client.delete_share() + await super().global_cleanup() + + async def close(self): + await self.async_share_client.close() + await super().close() + + +class _FileTest(_ShareTest): + def __init__(self, arguments): + super().__init__(arguments) + file_name = "sharefiletest-" + str(uuid.uuid4()) + self.sharefile_client = self.share_client.get_file_client(file_name) + self.async_sharefile_client = self.async_share_client.get_file_client(file_name) + + async def close(self): + await self.async_sharefile_client.close() + await super().close() diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/download.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/download.py new file mode 100644 index 000000000000..0ff3b6188d81 --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/download.py @@ -0,0 +1,36 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azure_devtools.perfstress_tests import get_random_bytes, WriteStream + +from ._test_base import _ShareTest + + +class DownloadTest(_ShareTest): + def __init__(self, arguments): + super().__init__(arguments) + file_name = "downloadtest" + self.sharefile_client = self.share_client.get_file_client(file_name) + self.async_sharefile_client = self.async_share_client.get_file_client(file_name) + self.download_stream = WriteStream() + + async def global_setup(self): + await super().global_setup() + data = get_random_bytes(self.args.size) + await self.async_sharefile_client.upload_file(data) + + def run_sync(self): + self.download_stream.reset() + stream = self.sharefile_client.download_file(max_concurrency=self.args.max_concurrency) + stream.readinto(self.download_stream) + + async def run_async(self): + self.download_stream.reset() + stream = await self.async_sharefile_client.download_file(max_concurrency=self.args.max_concurrency) + await stream.readinto(self.download_stream) + + async def close(self): + await self.async_sharefile_client.close() + await super().close() diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/download_to_file.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/download_to_file.py new file mode 100644 index 000000000000..807fffe69c9f --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/download_to_file.py @@ -0,0 +1,37 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import tempfile + +from azure_devtools.perfstress_tests import get_random_bytes + +from ._test_base import _ShareTest + + +class DownloadToFileTest(_ShareTest): + def __init__(self, arguments): + super().__init__(arguments) + file_name = "downloadtest" + self.sharefile_client = self.share_client.get_file_client(file_name) + self.async_sharefile_client = self.async_share_client.get_file_client(file_name) + + async def global_setup(self): + await super().global_setup() + data = get_random_bytes(self.args.size) + await self.async_sharefile_client.upload_file(data) + + def run_sync(self): + with tempfile.TemporaryFile() as fp: + stream = self.sharefile_client.download_file(max_concurrency=self.args.max_concurrency) + stream.readinto(fp) + + async def run_async(self): + with tempfile.TemporaryFile() as fp: + stream = await self.async_sharefile_client.download_file(max_concurrency=self.args.max_concurrency) + await stream.readinto(fp) + + async def close(self): + await self.async_sharefile_client.close() + await super().close() diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/upload.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/upload.py new file mode 100644 index 000000000000..65f06655bffd --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/upload.py @@ -0,0 +1,30 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from ._test_base import _FileTest + +from azure_devtools.perfstress_tests import RandomStream, get_random_bytes +from azure_devtools.perfstress_tests import AsyncRandomStream + + +class UploadTest(_FileTest): + def __init__(self, arguments): + super().__init__(arguments) + self.upload_stream = RandomStream(self.args.size) + self.upload_stream_async = AsyncRandomStream(self.args.size) + + def run_sync(self): + self.upload_stream.reset() + self.sharefile_client.upload_file( + self.upload_stream, + length=self.args.size, + max_concurrency=self.args.max_concurrency) + + async def run_async(self): + self.upload_stream_async.reset() + await self.async_sharefile_client.upload_file( + self.upload_stream_async, + length=self.args.size, + max_concurrency=self.args.max_concurrency) diff --git a/sdk/storage/azure-storage-file-share/tests/perfstress_tests/upload_from_file.py b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/upload_from_file.py new file mode 100644 index 000000000000..6530666f3248 --- /dev/null +++ b/sdk/storage/azure-storage-file-share/tests/perfstress_tests/upload_from_file.py @@ -0,0 +1,35 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import tempfile + +from azure_devtools.perfstress_tests import get_random_bytes + +from ._test_base import _FileTest + + +class UploadFromFileTest(_FileTest): + temp_file = None + + async def global_setup(self): + await super().global_setup() + data = get_random_bytes(self.args.size) + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + UploadFromFileTest.temp_file = temp_file.name + temp_file.write(data) + + async def global_cleanup(self): + if self.temp_file: + os.remove(UploadFromFileTest.temp_file) + await super().global_cleanup() + + def run_sync(self): + with open(UploadFromFileTest.temp_file, 'rb') as fp: + self.sharefile_client.upload_file(fp, max_concurrency=self.args.max_concurrency) + + async def run_async(self): + with open(UploadFromFileTest.temp_file, 'rb') as fp: + await self.async_sharefile_client.upload_file(fp, max_concurrency=self.args.max_concurrency) diff --git a/tools/azure-devtools/src/azure_devtools/perfstress_tests/async_random_stream.py b/tools/azure-devtools/src/azure_devtools/perfstress_tests/async_random_stream.py index 098a019fa36e..e483bc016afe 100644 --- a/tools/azure-devtools/src/azure_devtools/perfstress_tests/async_random_stream.py +++ b/tools/azure-devtools/src/azure_devtools/perfstress_tests/async_random_stream.py @@ -38,12 +38,6 @@ def read(self, size=None): self._remaining = self._remaining - e self._position += e return self._base_data[:e] - - def seek(self, index): - self._position = index - - def tell(self): - return self._position def seek(self, index, whence=0): if whence == 0: @@ -51,7 +45,7 @@ def seek(self, index, whence=0): elif whence == 1: self._position = self._position + index elif whence == 2: - self._position = self._length - 1 + index + self._position = self._data_length - 1 + index def tell(self): return self._position diff --git a/tools/azure-devtools/src/azure_devtools/perfstress_tests/random_stream.py b/tools/azure-devtools/src/azure_devtools/perfstress_tests/random_stream.py index 324a3eb67553..b68be9bfd806 100644 --- a/tools/azure-devtools/src/azure_devtools/perfstress_tests/random_stream.py +++ b/tools/azure-devtools/src/azure_devtools/perfstress_tests/random_stream.py @@ -53,7 +53,7 @@ def seek(self, index, whence=0): elif whence == 1: self._position = self._position + index elif whence == 2: - self._position = self._length - 1 + index + self._position = self._data_length - 1 + index def remaining(self): return self._remaining