Skip to content

Commit 1389360

Browse files
authored
Merge pull request #187 from Yelp/performance-check
Adding baseline functionality for benchmark script
2 parents f8682ab + 6629488 commit 1389360

File tree

6 files changed

+483
-30
lines changed

6 files changed

+483
-30
lines changed

Diff for: detect_secrets/util.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import os
2+
3+
4+
def get_root_directory():
5+
return os.path.realpath(
6+
os.path.join(
7+
os.path.dirname(__file__),
8+
'../',
9+
),
10+
)

Diff for: scripts/benchmark.py

+138-30
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
from monotonic import monotonic
1212

13+
from detect_secrets.core.color import AnsiColor
14+
from detect_secrets.core.color import colorize
1315
from detect_secrets.core.usage import PluginOptions
16+
from detect_secrets.util import get_root_directory
1417

1518

1619
def main():
@@ -92,7 +95,7 @@ def get_arguments():
9295
parser.add_argument(
9396
'--harakiri',
9497
default=5,
95-
type=float,
98+
type=assert_positive(float),
9699
help=(
97100
'Specifies an upper bound for the number of seconds to wait '
98101
'per execution.'
@@ -102,40 +105,59 @@ def get_arguments():
102105
'-n',
103106
'--num-iterations',
104107
default=1,
105-
type=assert_positive_integer,
108+
type=assert_positive(int),
106109
help=(
107110
'Specifies the number of times to run the test. '
108111
'Results will be averaged over this value.'
109112
),
110113
)
114+
parser.add_argument(
115+
'--baseline',
116+
type=assert_valid_file,
117+
help=(
118+
'If provided, will compare performance with provided baseline. '
119+
'Assumes pretty output (otherwise, you can do the comparison '
120+
'yourself).'
121+
),
122+
)
111123

112124
args = parser.parse_args()
113125
if not args.filenames:
114-
args.filenames = [
115-
os.path.realpath(
116-
os.path.join(
117-
os.path.dirname(__file__),
118-
'../',
119-
),
120-
),
121-
]
126+
if args.baseline:
127+
args.filenames = args.baseline['filenames']
128+
else:
129+
args.filenames = [get_root_directory()]
122130

123131
if not args.plugin:
124132
args.plugin = plugins
125133

126134
return args
127135

128136

129-
def assert_positive_integer(string):
130-
value = int(string)
131-
if value <= 0:
137+
def assert_positive(type):
138+
def wrapped(string):
139+
value = type(string)
140+
if value <= 0:
141+
raise argparse.ArgumentTypeError(
142+
'{} must be a positive {}.'.format(
143+
string,
144+
type.__name__,
145+
),
146+
)
147+
148+
return value
149+
150+
return wrapped
151+
152+
153+
def assert_valid_file(string):
154+
if not os.path.isfile(string):
132155
raise argparse.ArgumentTypeError(
133-
'{} must be a positive integer.'.format(
134-
string,
135-
),
156+
'{} must be a valid file.'.format(string),
136157
)
137158

138-
return value
159+
with open(string) as f:
160+
return json.load(f)
139161

140162

141163
def time_execution(filenames, timeout, num_iterations=1, flags=None):
@@ -166,39 +188,125 @@ def time_execution(filenames, timeout, num_iterations=1, flags=None):
166188
if result == timeout:
167189
return None
168190

169-
return statistics.mean(scores)
191+
return round(statistics.mean(scores), 5)
170192

171193

172194
def print_output(timings, args):
173195
"""
174196
:type timings: dict
175197
:type args: Namespace
176198
"""
177-
if not args.pretty:
178-
print(json.dumps(timings))
199+
if not args.pretty and not args.baseline:
200+
print(
201+
json.dumps({
202+
'filenames': args.filenames,
203+
'timings': timings,
204+
}),
205+
)
179206
return
180207

181208
# Print header
182-
print('-' * 42)
183-
print('{:<20s}{:>20s}'.format('plugin', 'time'))
184-
print('-' * 42)
209+
baseline = args.baseline['timings'] if args.baseline else {}
210+
if not baseline:
211+
print('-' * 45)
212+
print('{:<25s}{:>15s}'.format('plugin', 'time'))
213+
print('-' * 45)
214+
else:
215+
print('-' * 60)
216+
print('{:<25s}{:>11s}{:>22s}'.format('plugin', 'time', 'change'))
217+
print('-' * 60)
185218

219+
# Print content
186220
if 'all-plugins' in timings:
187-
print_line('all-plugins', timings['all-plugins'])
221+
print_line(
222+
'All Plugins',
223+
time=timings['all-plugins'],
224+
baseline=_get_baseline_value(baseline, 'all-plugins'),
225+
timeout=args.harakiri,
226+
)
188227
del timings['all-plugins']
189228

190229
for key in sorted(timings):
191-
print_line(key, timings[key])
192-
print('-' * 42)
230+
print_line(
231+
key,
232+
time=timings[key],
233+
baseline=_get_baseline_value(baseline, key),
234+
timeout=args.harakiri,
235+
)
193236

237+
# Print footer line
238+
if not args.baseline:
239+
print('-' * 45)
240+
else:
241+
print('-' * 60)
242+
243+
244+
def _get_baseline_value(baseline, key):
245+
"""
246+
We need to distinguish between no baseline mode (which should return
247+
None as a value), baseline mode with exceeded timeout (which is stored
248+
as None, but should return 0).
249+
"""
250+
if key in baseline:
251+
return 0 if baseline[key] is None else baseline[key]
252+
253+
254+
def print_line(name, time, baseline, timeout):
255+
"""
256+
:type name: str
257+
258+
:type time: float
259+
:param time: seconds it took to execute
260+
261+
:type baseline: float
262+
:param baseline: expected seconds to execute
194263
195-
def print_line(name, time):
264+
:type timeout: float
265+
:param timeout: used to calculate difference when either current
266+
execution or baseline execution exceeds timeout.
267+
"""
196268
if not time:
197-
time = 'Timeout exceeded!'
269+
time_string = 'Timeout exceeded!'
198270
else:
199-
time = '{}s'.format(str(time))
271+
time_string = '{}s'.format(str(time))
272+
273+
if baseline is not None:
274+
if time and baseline:
275+
difference = round(baseline - time, 2)
276+
elif time:
277+
# This handles the case when the baseline execution exceeds timeout
278+
difference = round(timeout - time, 2)
279+
elif baseline:
280+
# This handles the case when this current execution exceeds timeout
281+
difference = round(timeout - baseline, 2)
282+
else:
283+
# They both failed.
284+
difference = 0
285+
286+
if difference > 0:
287+
difference_string = colorize(
288+
'▲ {}'.format(difference),
289+
AnsiColor.LIGHT_GREEN,
290+
)
291+
difference_string = '{:>22s}'.format(difference_string)
292+
elif difference < 0:
293+
difference_string = colorize(
294+
'▼ {}'.format(difference),
295+
AnsiColor.RED,
296+
)
297+
difference_string = '{:>22s}'.format(difference_string)
298+
else:
299+
difference_string = '{:>10s}'.format('-')
200300

201-
print('{:<20s}{:>20s}'.format(name, time))
301+
print(
302+
'{:<25s}{:^20s}{}'.format(
303+
name,
304+
time_string,
305+
difference_string,
306+
),
307+
)
308+
else:
309+
print('{:<25s}{:>20s}'.format(name, time_string))
202310

203311

204312
if __name__ == '__main__':

0 commit comments

Comments
 (0)