Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/scripts/performance/benchmark_utils.py
1566 views
1
import s3transfer
2
import os
3
import subprocess
4
import uuid
5
import shutil
6
import argparse
7
import tempfile
8
9
10
def summarize(script, result_dir, summary_dir):
11
"""Run the given summary script on every file in the given directory.
12
13
:param script: A summarization script that takes a list of csv files.
14
:param result_dir: A directory containing csv performance result files.
15
:param summary_dir: The directory to put the summary file in.
16
"""
17
summarize_args = [script]
18
for f in os.listdir(result_dir):
19
path = os.path.join(result_dir, f)
20
if os.path.isfile(path):
21
summarize_args.append(path)
22
23
with open(os.path.join(summary_dir, 'summary.txt'), 'wb') as f:
24
subprocess.check_call(summarize_args, stdout=f)
25
with open(os.path.join(summary_dir, 'summary.json'), 'wb') as f:
26
summarize_args.extend(['--output-format', 'json'])
27
subprocess.check_call(summarize_args, stdout=f)
28
29
30
def _get_s3transfer_performance_script(script_name):
31
"""Retrieves an s3transfer performance script if available."""
32
s3transfer_directory = os.path.dirname(s3transfer.__file__)
33
s3transfer_directory = os.path.dirname(s3transfer_directory)
34
scripts_directory = 'scripts/performance'
35
scripts_directory = os.path.join(s3transfer_directory, scripts_directory)
36
script = os.path.join(scripts_directory, script_name)
37
38
if os.path.isfile(script):
39
return script
40
else:
41
return None
42
43
44
def get_benchmark_script():
45
return _get_s3transfer_performance_script('benchmark')
46
47
48
def get_summarize_script():
49
return _get_s3transfer_performance_script('summarize')
50
51
52
def backup(source, recursive):
53
"""Backup a given source to a temporary location.
54
55
:type source: str
56
:param source: A local path or s3 path to backup.
57
58
:type recursive: bool
59
:param recursive: if True, the source will be treated as a directory.
60
"""
61
if source[:5] == 's3://':
62
parts = source.split('/')
63
parts.insert(3, str(uuid.uuid4()))
64
backup_path = '/'.join(parts)
65
else:
66
name = os.path.split(source)[-1]
67
temp_dir = tempfile.mkdtemp()
68
backup_path = os.path.join(temp_dir, name)
69
70
copy(source, backup_path, recursive)
71
return backup_path
72
73
74
def copy(source, destination, recursive):
75
"""Copy files from one location to another.
76
77
The source and destination must both be s3 paths or both be local paths.
78
79
:type source: str
80
:param source: A local path or s3 path to backup.
81
82
:type destination: str
83
:param destination: A local path or s3 path to backup the source to.
84
85
:type recursive: bool
86
:param recursive: if True, the source will be treated as a directory.
87
"""
88
if 's3://' in [source[:5], destination[:5]]:
89
cp_args = ['aws', 's3', 'cp', source, destination, '--quiet']
90
if recursive:
91
cp_args.append('--recursive')
92
subprocess.check_call(cp_args)
93
return
94
95
if recursive:
96
shutil.copytree(source, destination)
97
else:
98
shutil.copy(source, destination)
99
100
101
def clean(destination, recursive):
102
"""Delete a file or directory either locally or on S3."""
103
if destination[:5] == 's3://':
104
rm_args = ['aws', 's3', 'rm', '--quiet', destination]
105
if recursive:
106
rm_args.append('--recursive')
107
subprocess.check_call(rm_args)
108
else:
109
if recursive:
110
shutil.rmtree(destination)
111
else:
112
os.remove(destination)
113
114
115
def create_random_subfolder(destination):
116
"""Create a random subdirectory in a given directory."""
117
folder_name = str(uuid.uuid4())
118
if destination.startswith('s3://'):
119
parts = destination.split('/')
120
parts.append(folder_name)
121
return '/'.join(parts)
122
else:
123
parts = list(os.path.split(destination))
124
parts.append(folder_name)
125
path = os.path.join(*parts)
126
os.makedirs(path)
127
return path
128
129
130
def get_transfer_command(command, recursive, quiet):
131
"""Get a full cli transfer command.
132
133
Performs common transformations, e.g. adding --quiet
134
"""
135
cli_command = 'aws s3 ' + command
136
137
if recursive:
138
cli_command += ' --recursive'
139
140
if quiet:
141
cli_command += ' --quiet'
142
else:
143
print(cli_command)
144
145
return cli_command
146
147
148
def benchmark_command(command, benchmark_script, summarize_script,
149
output_dir, num_iterations, dry_run, upkeep=None,
150
cleanup=None):
151
"""Benchmark several runs of a long-running command.
152
153
:type command: str
154
:param command: The full aws cli command to benchmark
155
156
:type benchmark_script: str
157
:param benchmark_script: A benchmark script that takes a command to run
158
and outputs performance data to a file. This should be from s3transfer.
159
160
:type summarize_script: str
161
:param summarize_script: A summarization script that the output of the
162
benchmark script. This should be from s3transfer.
163
164
:type output_dir: str
165
:param output_dir: The directory to output performance results to.
166
167
:type num_iterations: int
168
:param num_iterations: The number of times to run the benchmark on the
169
command.
170
171
:type dry_run: bool
172
:param dry_run: Whether or not to actually run the benchmarks.
173
174
:type upkeep: function that takes no arguments
175
:param upkeep: A function that is run after every iteration of the
176
benchmark process. This should be used for upkeep, such as restoring
177
files that were deleted as part of the command executing.
178
179
:type cleanup: function that takes no arguments
180
:param cleanup: A function that is run at the end of the benchmark
181
process or if there are any problems during the benchmark process.
182
It should be uses for the final cleanup, such as deleting files that
183
were created at some destination.
184
"""
185
performance_dir = os.path.join(output_dir, 'performance')
186
if os.path.exists(performance_dir):
187
shutil.rmtree(performance_dir)
188
os.makedirs(performance_dir)
189
190
try:
191
for i in range(num_iterations):
192
out_file = 'performance%s.csv' % i
193
out_file = os.path.join(performance_dir, out_file)
194
benchmark_args = [
195
benchmark_script, command, '--output-file', out_file
196
]
197
if not dry_run:
198
subprocess.check_call(benchmark_args)
199
if upkeep is not None:
200
upkeep()
201
202
if not dry_run:
203
summarize(summarize_script, performance_dir, output_dir)
204
finally:
205
if not dry_run and cleanup is not None:
206
cleanup()
207
208
209
def get_default_argparser():
210
"""Get an ArgumentParser with all the base benchmark arguments added in."""
211
parser = argparse.ArgumentParser()
212
parser.add_argument(
213
'--no-cleanup', action='store_true', default=False,
214
help='Do not remove the destination after the tests complete.'
215
)
216
parser.add_argument(
217
'--recursive', action='store_true', default=False,
218
help='Indicates that this is a recursive transfer.'
219
)
220
benchmark_script = get_benchmark_script()
221
parser.add_argument(
222
'--benchmark-script', default=benchmark_script,
223
required=benchmark_script is None,
224
help=('The benchmark script to run the commands with. This should be '
225
'from s3transfer.')
226
)
227
summarize_script = get_summarize_script()
228
parser.add_argument(
229
'--summarize-script', default=summarize_script,
230
required=summarize_script is None,
231
help=('The summarize script to run the commands with. This should be '
232
'from s3transfer.')
233
)
234
parser.add_argument(
235
'-o', '--result-dir', default='results',
236
help='The directory to output performance results to. Existing '
237
'results will be deleted.'
238
)
239
parser.add_argument(
240
'--dry-run', default=False, action='store_true',
241
help='If set, commands will only be printed out, not executed.'
242
)
243
parser.add_argument(
244
'--quiet', default=False, action='store_true',
245
help='If set, output is suppressed.'
246
)
247
parser.add_argument(
248
'-n', '--num-iterations', default=1, type=int,
249
help='The number of times to run the test.'
250
)
251
return parser
252
253