Skip to content

Commit aca9512

Browse files
authored
Merge pull request #539 from ahoppen/pr/analyze-code-completion-timings
[Stress Tester] Use new duration measurement capability of stress tester to analyse code completion durations
2 parents ae7dfcb + b860c6c commit aca9512

File tree

2 files changed

+148
-2
lines changed

2 files changed

+148
-2
lines changed

analyze_request_durations.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/env python
2+
3+
from __future__ import print_function
4+
5+
import argparse
6+
import json
7+
import os
8+
9+
# This value was emirically determined on an iMac Pro with a fairly small sample size.
10+
# All measurements in seconds should be taken with extreme care
11+
INSTRUCTIONS_PER_SECOND = 6693088549.09358
12+
13+
# The length of the bars in the histograms
14+
HISTOGRAM_BAR_LENGTH = 40
15+
16+
17+
def parse_args():
18+
parser = argparse.ArgumentParser()
19+
parser.add_argument('request_durations_file')
20+
return parser.parse_args()
21+
22+
23+
def print_stats(entries):
24+
"""
25+
entries is a list of dictionaries from the JSON file under the
26+
files.<file-name>.<request-name> path containing the following keys
27+
- totalInstructions
28+
- logHistogram
29+
- requestsExecuted
30+
"""
31+
32+
# Create a log histogram for all entries
33+
aggregated_completion_log_histogram = {}
34+
# Adds totalInstruction for each entry
35+
total_instructions = 0
36+
# Adds requestsExecuted for each entry
37+
total_requests_executed = 0
38+
for entry in entries:
39+
total_instructions += int(entry['totalInstructions'])
40+
total_requests_executed += int(entry['requestsExecuted'])
41+
for (bucket, count) in entry['logHistogram'].iteritems():
42+
bucket = int(bucket)
43+
count = int(count)
44+
if bucket not in aggregated_completion_log_histogram:
45+
aggregated_completion_log_histogram[bucket] = 0
46+
aggregated_completion_log_histogram[bucket] += count
47+
48+
if total_requests_executed == 0:
49+
print('No requests executed')
50+
return
51+
52+
print('Requests: {}'.format(total_requests_executed))
53+
54+
average_instructions_per_request = total_instructions / total_requests_executed
55+
seconds_estimate = average_instructions_per_request / INSTRUCTIONS_PER_SECOND
56+
print('Average: {:.0e} instr (ca. {:.3f}s)'
57+
.format(average_instructions_per_request, seconds_estimate))
58+
59+
print('\nHistogram:')
60+
61+
min_bucket = min(aggregated_completion_log_histogram)
62+
max_bucket = max(aggregated_completion_log_histogram)
63+
max_count = max([x[1] for x in aggregated_completion_log_histogram.iteritems()])
64+
65+
# Sums up the percentages for each entry to make it easier to spot the
66+
# n-th percentile in the histogram
67+
running_percentage_sum = 0
68+
for bucket in range(min_bucket, max_bucket + 1):
69+
count = aggregated_completion_log_histogram.get(bucket, 0)
70+
executed_instructions = pow(2, bucket)
71+
estimated_seconds = executed_instructions / INSTRUCTIONS_PER_SECOND
72+
bar_fill = (count * HISTOGRAM_BAR_LENGTH / max_count)
73+
bar = '*' * bar_fill + ' ' * (HISTOGRAM_BAR_LENGTH - bar_fill)
74+
percentage = float(count) / total_requests_executed * 100
75+
running_percentage_sum += percentage
76+
print('< {:5.0e} instr (ca. {:6.3f}s): {} {:4.1f}% (< {:5.1f}%)'
77+
.format(executed_instructions, estimated_seconds, bar, percentage,
78+
running_percentage_sum))
79+
80+
81+
def print_stats_for_all_files(request_durations, request_name):
82+
print('\n---------- All {} requests ----------\n'.format(request_name))
83+
all_request_entries = []
84+
for (file_name, file_stats) in request_durations['files'].iteritems():
85+
all_request_entries.append(file_stats[request_name])
86+
print_stats(all_request_entries)
87+
88+
89+
def print_slowest_files(request_durations, request_name, num_files):
90+
print('\n---------- {} slowest files for {} requests ----------\n'
91+
.format(num_files, request_name))
92+
93+
file_and_average = []
94+
for (file_name, file_stats) in request_durations['files'].iteritems():
95+
instr_executed_in_file = int(file_stats[request_name]['totalInstructions'])
96+
requests_in_file = int(file_stats[request_name]['requestsExecuted'])
97+
if requests_in_file > 0:
98+
average = instr_executed_in_file / requests_in_file
99+
file_and_average.append((file_name, average))
100+
file_and_average = sorted(file_and_average, key=lambda x: x[1], reverse=True)
101+
102+
for (file_name, average_request_duration) in file_and_average[0:num_files]:
103+
print('-- {} --\n'.format(file_name))
104+
print_stats([request_durations['files'][file_name][request_name]])
105+
print('\n')
106+
107+
108+
def print_seconds_disclaimer():
109+
print('''IMPORTANT:
110+
All measurements printed by this script were done in number of number of
111+
instructions. Measurements in seconds are computed assuming that all machines
112+
execute the same number of instructions per second at all times, which is not
113+
correct. Thus all measurements in seconds should be taken with extreme care.
114+
As they scale linearly with the number of instructions, it is safe to compare
115+
values in seconds output by this script.
116+
''')
117+
118+
119+
def analyze_request_durations(file):
120+
if not os.path.exists(file):
121+
print('{} does not exist. No request durations to analyze.'.format(file))
122+
return
123+
print_seconds_disclaimer()
124+
print('\n---------- Raw durations for further analysis ----------\n')
125+
with open(file, 'r') as json_file:
126+
print(json_file.read())
127+
128+
with open(file, 'r') as json_file:
129+
request_durations = json.load(json_file, encoding='utf-8')
130+
print_stats_for_all_files(request_durations, 'CodeComplete')
131+
print_slowest_files(request_durations, 'CodeComplete', 10)
132+
133+
134+
def main():
135+
args = parse_args()
136+
analyze_request_durations(args.request_durations_file)
137+
138+
139+
if __name__ == '__main__':
140+
main()

run_sk_stress_test

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import subprocess
2222
import argparse
2323
import platform
2424
import codecs
25+
from analyze_request_durations import analyze_request_durations
2526

2627
import common
2728

@@ -230,9 +231,10 @@ class StressTesterRunner(object):
230231
# temporary file paths
231232
filtered_projects = os.path.join(script_dir, 'stress-tester-projects.json')
232233
results = os.path.join(script_dir, 'stress-tester-results.json')
234+
request_durations = os.path.join(script_dir, 'stress-tester-request-durations.json')
233235

234236
# remove temporary files if they already exist
235-
self._cleanup([filtered_projects, results])
237+
self._cleanup([filtered_projects, results, request_durations])
236238

237239
run_env = {
238240
'SK_STRESS_TEST': self.stress_tester,
@@ -243,7 +245,9 @@ class StressTesterRunner(object):
243245
'SK_XFAILS_PATH': self.xfails_path,
244246
'SK_STRESS_ACTIVE_CONFIG': self.swift_branch,
245247
'SK_STRESS_SUPPRESS_OUTPUT': 'true',
246-
'SK_STRESS_REWRITE_MODES': 'none concurrent insideout'}
248+
'SK_STRESS_REWRITE_MODES': 'none concurrent insideout',
249+
'SK_STRESS_REQUEST_DURATIONS_FILE': request_durations,
250+
}
247251
run_env.update(os.environ)
248252
run_cmd = ['./runner.py',
249253
'--projects', filtered_projects,
@@ -264,6 +268,8 @@ class StressTesterRunner(object):
264268
except common.ExecuteCommandFailure:
265269
self.compat_runner_failed = True
266270

271+
analyze_request_durations(request_durations)
272+
267273
success = self._process_output(results, self.xfails_path)
268274

269275
return success

0 commit comments

Comments
 (0)