Skip to content

Commit

Permalink
[Perfstress][Storage] Added FileShare perf tests (#15834)
Browse files Browse the repository at this point in the history
* Fileshare perf tests

* Memory management

* Fix legacy test memory

* Fix upload test

* Fix file upload test

* Fix perf stream

* Removed global cleanup

* Update stream support

* Remove useless setup

* Added readme

* Ignore readme

* Fix stream merge

* Fix readme

* Fix stream merge

* Fix async stream
  • Loading branch information
annatisch authored Mar 3, 2021
1 parent 4f1c4a7 commit 0ab62f4
Show file tree
Hide file tree
Showing 16 changed files with 486 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -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=<live storage account 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
```
Empty file.
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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.")
Original file line number Diff line number Diff line change
@@ -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.")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
azure-storage-file==2.1.0
Original file line number Diff line number Diff line change
@@ -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.")
Original file line number Diff line number Diff line change
@@ -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.")
Empty file.
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit 0ab62f4

Please sign in to comment.