Skip to content

Commit f6393d6

Browse files
authored
[lit] Add configurable slowest-test limit to --time-tests (#208444)
The PR parameterizes `--time-tests`, which was hardcoded to 20 tests. We now allow `--time-tests=N` or `--time-tests=all`. The changes still honor the original behavior of `--time-tests`. The parsing logic could be slightly simpler if `--time-tests` would always require an explicit number of tests (like `-j`), but that would be a CLI breaking change.
1 parent aa59606 commit f6393d6

7 files changed

Lines changed: 112 additions & 19 deletions

File tree

llvm/docs/CommandGuide/lit.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,15 @@ EXECUTION OPTIONS
254254

255255
Do not track elapsed wall time for each test.
256256

257-
.. option:: --time-tests
257+
.. option:: --time-tests[=N|all]
258258

259-
Track the wall time individual tests take to execute and includes the results
259+
Track the wall time individual tests take to execute and include the results
260260
in the summary output. This is useful for determining which tests in a test
261-
suite take the most time to execute.
261+
suite take the most time to execute. When enabled, lit prints a slowest-test
262+
list and a histogram over all timed tests. The slowest-test list defaults to
263+
the 20 slowest tests, but can be limited with ``=N`` or expanded to every
264+
timed test with ``=all``. The headings report how many tests are listed, for
265+
example ``Slowest Tests (N of M):`` and ``Test Times (M):``.
262266

263267
.. _selection-options:
264268

llvm/utils/lit/lit/cl_arguments.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ def setOutputLevel(cls, namespace, dest, value):
5555
setattr(namespace, "test_output", value)
5656

5757

58+
_TIME_TESTS_OPT = "--time-tests"
59+
_TIME_TESTS_SLOWEST_DEFAULT = 20
60+
_TIME_TESTS_PREFIX = f"{_TIME_TESTS_OPT}="
61+
5862
class AliasAction(argparse.Action):
5963
def __init__(self, option_strings, dest, nargs=None, **kwargs):
6064
self.expansion = kwargs.pop("alias", None)
@@ -382,9 +386,12 @@ def parse_args():
382386
action="store_true",
383387
)
384388
execution_test_time_group.add_argument(
385-
"--time-tests",
386-
help="Track elapsed wall time for each test printed in a histogram",
389+
_TIME_TESTS_OPT,
390+
help="Track elapsed wall time for each test and print a histogram; "
391+
"optionally limit the slowest-test list with =N or report all with =all "
392+
f"(default slowest count: {_TIME_TESTS_SLOWEST_DEFAULT})",
387393
action="store_true",
394+
default=None,
388395
)
389396

390397
selection_group = parser.add_argument_group("Test Selection")
@@ -515,8 +522,21 @@ def parse_args():
515522

516523
# LIT is special: environment variables override command line arguments.
517524
env_args = shlex.split(os.environ.get("LIT_OPTS", ""))
518-
args = sys.argv[1:] + env_args
525+
try:
526+
# --time-tests is preprocessed here: bare --time-tests defaults to 20,
527+
# and --time-tests=N / --time-tests=all set the slowest-test limit.
528+
# Values must use = (e.g. --time-tests=all), since `lit --time-tests all`
529+
# could mean "show all slow tests" or "run the test directory all".
530+
args, time_tests = _extract_time_tests_args(sys.argv[1:] + env_args)
531+
except argparse.ArgumentTypeError as exc:
532+
parser.error(str(exc))
519533
opts = parser.parse_args(args)
534+
opts.time_tests = time_tests
535+
536+
if opts.time_tests is not None and opts.skip_test_time_recording:
537+
parser.error(
538+
f"argument --skip-test-time-recording: not allowed with argument {_TIME_TESTS_OPT}"
539+
)
520540

521541
# Validate command line options
522542
if opts.incremental:
@@ -555,6 +575,28 @@ def _positive_int(arg):
555575
return _int(arg, "positive", lambda i: i > 0)
556576

557577

578+
def _time_tests_count(arg):
579+
if arg.lower() == "all":
580+
return "all"
581+
return _positive_int(arg)
582+
583+
584+
def _extract_time_tests_args(args):
585+
processed = []
586+
time_tests = None
587+
for arg in args:
588+
if arg == _TIME_TESTS_OPT:
589+
time_tests = _TIME_TESTS_SLOWEST_DEFAULT
590+
elif arg.startswith(_TIME_TESTS_PREFIX):
591+
value = arg[len(_TIME_TESTS_PREFIX) :]
592+
if not value:
593+
raise _error(f"argument {_TIME_TESTS_OPT} requires a value after '='")
594+
time_tests = _time_tests_count(value)
595+
else:
596+
processed.append(arg)
597+
return processed, time_tests
598+
599+
558600
def _non_negative_int(arg):
559601
return _int(arg, "non-negative", lambda i: i >= 0)
560602

llvm/utils/lit/lit/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def main(builtin_params={}):
127127
)
128128

129129
if opts.time_tests:
130-
print_histogram(discovered_tests)
130+
print_histogram(discovered_tests, opts.time_tests)
131131

132132
print_results(discovered_tests, elapsed, opts)
133133

@@ -315,12 +315,12 @@ def execute_in_tmp_dir(run, lit_config):
315315
)
316316

317317

318-
def print_histogram(tests):
318+
def print_histogram(tests, slowest_limit):
319319
test_times = [
320320
(t.getFullName(), t.result.elapsed) for t in tests if t.result.elapsed
321321
]
322322
if test_times:
323-
lit.util.printHistogram(test_times, title="Tests")
323+
lit.util.printHistogram(test_times, slowest_limit, title="Tests")
324324

325325

326326
def print_results(tests, elapsed, opts):

llvm/utils/lit/lit/util.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,13 @@ def whichTools(tools, paths):
158158
return None
159159

160160

161-
def printHistogram(items, title="Items"):
161+
def printHistogram(items, slowest_limit, title="Items"):
162162
items.sort(key=lambda item: item[1])
163+
total = len(items)
164+
if slowest_limit == "all":
165+
slowest_count = total
166+
else:
167+
slowest_count = min(slowest_limit, total)
163168

164169
maxValue = max([v for _, v in items])
165170

@@ -180,11 +185,11 @@ def printHistogram(items, title="Items"):
180185

181186
barW = 40
182187
hr = "-" * (barW + 34)
183-
print("Slowest %s:" % title)
188+
print("Slowest %s (%d of %d):" % (title, slowest_count, total))
184189
print(hr)
185-
for name, value in reversed(items[-20:]):
190+
for name, value in reversed(items[-slowest_count:]):
186191
print("%.2fs: %s" % (value, name))
187-
print("\n%s Times:" % title)
192+
print("\nTest Times (%d):" % total)
188193
print(hr)
189194
pDigits = int(math.ceil(math.log(maxValue, 10)))
190195
pfDigits = max(0, 3 - pDigits)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# RUN: true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# RUN: true

llvm/utils/lit/tests/time-tests.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,53 @@
33
# RUN: %{lit-no-order-opt} --skip-test-time-recording %{inputs}/time-tests
44
# RUN: not ls %{inputs}/time-tests/.lit_test_times.txt
55

6-
## Check that --time-tests generates a printed histogram.
6+
## Check that --time-tests (default 20 tests) generates a printed histogram.
7+
# The slowest-test entries are matched with -DAG in any order to avoid
8+
# performance-wise flakiness from relying on exact execution-time ordering.
79

810
# RUN: %{lit-no-order-opt} --time-tests %{inputs}/time-tests > %t.out
9-
# RUN: FileCheck < %t.out %s
11+
# RUN: FileCheck --check-prefix=DEFAULT < %t.out %s
1012
# RUN: rm %{inputs}/time-tests/.lit_test_times.txt
1113

12-
# CHECK: Tests Times:
13-
# CHECK-NEXT: --------------------------------------------------------------------------
14-
# CHECK-NEXT: [ Range ] :: [ Percentage ] :: [Count]
15-
# CHECK-NEXT: --------------------------------------------------------------------------
14+
# DEFAULT: Slowest Tests (3 of 3):
15+
# DEFAULT-DAG: {{[0-9.]+}}s: time-tests :: a.txt
16+
# DEFAULT-DAG: {{[0-9.]+}}s: time-tests :: b.txt
17+
# DEFAULT-DAG: {{[0-9.]+}}s: time-tests :: c.txt
18+
# DEFAULT: Test Times (3):
19+
# DEFAULT-NEXT: --------------------------------------------------------------------------
20+
# DEFAULT-NEXT: [ Range ] :: [ Percentage ] :: [Count]
21+
# DEFAULT-NEXT: --------------------------------------------------------------------------
22+
23+
## Check that --time-tests=1 limits the slowest-test list.
24+
25+
# RUN: %{lit-no-order-opt} --time-tests=1 %{inputs}/time-tests > %t.one.out
26+
# RUN: FileCheck --check-prefix=ONE < %t.one.out %s
27+
# RUN: rm %{inputs}/time-tests/.lit_test_times.txt
28+
29+
# ONE: Slowest Tests (1 of 3):
30+
# ONE-COUNT-1: {{[0-9.]+}}s: time-tests ::
31+
# ONE: Test Times (3):
32+
33+
## Check that --time-tests=all reports every timed test.
34+
35+
# RUN: %{lit-no-order-opt} --time-tests=all %{inputs}/time-tests > %t.all.out
36+
# RUN: FileCheck --check-prefix=ALL < %t.all.out %s
37+
# RUN: rm %{inputs}/time-tests/.lit_test_times.txt
38+
39+
# ALL: Slowest Tests (3 of 3):
40+
# ALL-DAG: {{[0-9.]+}}s: time-tests :: a.txt
41+
# ALL-DAG: {{[0-9.]+}}s: time-tests :: b.txt
42+
# ALL-DAG: {{[0-9.]+}}s: time-tests :: c.txt
43+
# ALL: Test Times (3):
44+
45+
## Check that invalid --time-tests values are rejected.
46+
47+
# RUN: not %{lit-no-order-opt} --time-tests=0 %{inputs}/time-tests 2>&1 | FileCheck %s --check-prefix=INVALID
48+
49+
# INVALID: requires positive integer
50+
51+
## Check that malformed --time-tests values with extra '=' are rejected.
52+
53+
# RUN: not %{lit-no-order-opt} --time-tests=all=foo %{inputs}/time-tests 2>&1 | FileCheck %s --check-prefix=MALFORMED
54+
55+
# MALFORMED: requires positive integer, but found 'all=foo'

0 commit comments

Comments
 (0)