Skip to content

Commit 3ff8dfb

Browse files
committed
litsupport/remote: Work without shared filesystem
Change litsupport/remote code to not assume a shared filesystem anymore: - Assumes we can run commands on the remote target and get output/returncodes back (typically ssh) - Expects the benchmark build directory was transfered to the remove device via other means (typically rsync) before running. Differential Revision: https://reviews.llvm.org/D51080 llvm-svn: 341257
1 parent 41d067c commit 3ff8dfb

File tree

10 files changed

+145
-55
lines changed

10 files changed

+145
-55
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ set(TEST_SUITE_REMOTE_CLIENT "ssh" CACHE STRING "Remote execution client")
6666
set(TEST_SUITE_REMOTE_HOST "" CACHE STRING "Remote execution host")
6767
mark_as_advanced(TEST_SUITE_REMOTE_CLIENT)
6868

69+
add_custom_target(rsync
70+
COMMAND ${PROJECT_SOURCE_DIR}/utils/rsync.sh
71+
${TEST_SUITE_REMOTE_HOST} ${PROJECT_BINARY_DIR}
72+
USES_TERMINAL
73+
)
74+
6975
# Run Under configuration for RunSafely.sh (will be set in lit.site.cfg)
7076
set(TEST_SUITE_RUN_UNDER "" CACHE STRING "RunSafely.sh run-under (-u) parameter")
7177

cmake/modules/TestSuite.cmake

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
include(TestFile)
77
include(CopyDir)
88

9+
set(_DEFAULT_TEST_SUITE_COPY_DATA OFF)
10+
if(TEST_SUITE_REMOTE_HOST)
11+
set(_DEFAULT_TEST_SUITE_COPY_DATA ON)
12+
endif()
13+
option(TEST_SUITE_COPY_DATA "Always copy benchmark data to builddir"
14+
${_DEFAULT_TEST_SUITE_COPY_DATA})
15+
mark_as_advanced(TEST_SUITE_COPY_DATA)
16+
917
# Copies files and directories to be used as benchmark input data to the
1018
# directory of the benchmark executable.
1119
# Paths are interepreted relative to CMAKE_CURRENT_SOURCE_DIR by default but
@@ -18,7 +26,7 @@ function(llvm_test_data target)
1826
endif()
1927
foreach(file ${_LTDARGS_UNPARSED_ARGUMENTS})
2028
set(full_path ${SOURCE_DIR}/${file})
21-
if(_LTDARGS_MUST_COPY)
29+
if(_LTDARGS_MUST_COPY OR TEST_SUITE_COPY_DATA)
2230
if(IS_DIRECTORY ${full_path})
2331
llvm_copy_dir(${target} $<TARGET_FILE_DIR:${target}>/${file} ${full_path})
2432
else()
@@ -87,9 +95,9 @@ endfunction()
8795
function(test_suite_add_build_dependencies target)
8896
add_dependencies(${target}
8997
build-HashProgramOutput.sh
90-
build-fpcmp
9198
build-timeit
9299
build-timeit-target
100+
fpcmp
93101
)
94102
endfunction()
95103

litsupport/modules/microbenchmark.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,23 @@ def _mutateScript(context, script):
2424

2525
def _collectMicrobenchmarkTime(context, microbenchfiles):
2626
for f in microbenchfiles:
27-
with open(f) as inp:
28-
lines = csv.reader(inp)
29-
# First line: "name,iterations,real_time,cpu_time,time_unit..."
30-
for line in lines:
31-
if line[0] == 'name':
32-
continue
33-
# Name for MicroBenchmark
34-
name = line[0]
35-
# Create Result object with PASS
36-
microBenchmark = lit.Test.Result(lit.Test.PASS)
37-
38-
# Index 3 is cpu_time
39-
exec_time_metric = lit.Test.toMetricValue(float(line[3]))
40-
microBenchmark.addMetric('exec_time', exec_time_metric)
41-
42-
# Add Micro Result
43-
context.micro_results[name] = microBenchmark
27+
content = context.read_result_file(context, f)
28+
lines = csv.reader(content.splitlines())
29+
# First line: "name,iterations,real_time,cpu_time,time_unit..."
30+
for line in lines:
31+
if line[0] == 'name':
32+
continue
33+
# Name for MicroBenchmark
34+
name = line[0]
35+
# Create Result object with PASS
36+
microBenchmark = lit.Test.Result(lit.Test.PASS)
37+
38+
# Index 3 is cpu_time
39+
exec_time_metric = lit.Test.toMetricValue(float(line[3]))
40+
microBenchmark.addMetric('exec_time', exec_time_metric)
41+
42+
# Add Micro Result
43+
context.micro_results[name] = microBenchmark
4444

4545
# returning the number of microbenchmarks collected as a metric for the
4646
# base test

litsupport/modules/profilegen.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def mutatePlan(context, plan):
2020
context.profilefiles = []
2121
# Adjust run steps to set LLVM_PROFILE_FILE environment variable.
2222
plan.runscript = _mutateScript(context, plan.runscript)
23+
plan.profile_files += context.profilefiles
24+
2325
# Run profdata merge at the end
2426
profdatafile = context.executable + ".profdata"
2527
args = ['merge', '-output=%s' % profdatafile] + context.profilefiles

litsupport/modules/remote.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,51 @@
33
device (typically shared by NFS)."""
44
from litsupport import testplan
55
import logging
6+
import os
7+
import subprocess
68

79

8-
def _mutateCommandline(context, commandline, suffix=""):
9-
shfilename = context.tmpBase + suffix + ".sh"
10-
shfile = open(shfilename, "w")
11-
shfile.write(commandline + "\n")
12-
logging.info("Created shfile '%s'", shfilename)
13-
shfile.close()
10+
def _wrap_command(context, command):
11+
escaped_command = command.replace("'", "'\\''")
12+
return "%s %s '%s'" % (context.config.remote_client,
13+
context.config.remote_host, escaped_command)
1414

15-
config = context.config
16-
remote_commandline = config.remote_client
17-
remote_commandline += " %s" % config.remote_host
18-
remote_commandline += " /bin/sh %s" % shfilename
19-
return remote_commandline
2015

16+
def _mutateCommandline(context, commandline):
17+
return _wrap_command(context, commandline)
2118

22-
def _mutateScript(context, script, suffix=""):
19+
20+
def _mutateScript(context, script):
2321
def mutate(context, command):
24-
return _mutateCommandline(context, command, suffix)
22+
return _mutateCommandline(context, command)
2523
return testplan.mutateScript(context, script, mutate)
2624

2725

26+
def remote_read_result_file(context, path):
27+
assert os.path.isabs(path)
28+
command = _wrap_command(context, "cat '%s'" % path)
29+
logging.info("$ %s", command)
30+
return subprocess.check_output(command, shell=True)
31+
32+
2833
def mutatePlan(context, plan):
29-
plan.preparescript = _mutateScript(context, plan.preparescript, "-prepare")
34+
plan.preparescript = _mutateScript(context, plan.preparescript)
35+
# We need the temporary directory to exist on the remote as well.
36+
command = _wrap_command(context,
37+
"mkdir -p '%s'" % os.path.dirname(context.tmpBase))
38+
plan.preparescript.insert(0, command)
3039
plan.runscript = _mutateScript(context, plan.runscript)
40+
plan.verifyscript = _mutateScript(context, plan.verifyscript)
41+
for name, script in plan.metricscripts.items():
42+
plan.metricscripts[name] = _mutateScript(context, script)
43+
44+
# Merging profile data should happen on the host because that is where
45+
# the toolchain resides, however we have to retrieve the profile data
46+
# from the device first, add commands for that to the profile script.
47+
for path in plan.profile_files:
48+
assert os.path.isabs(path)
49+
command = "scp %s:%s %s" % (context.config.remote_host, path, path)
50+
plan.profilescript.insert(0, command)
51+
52+
assert context.read_result_file is testplan.default_read_result_file
53+
context.read_result_file = remote_read_result_file

litsupport/modules/timeit.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66

77
def _mutateCommandLine(context, commandline):
8-
outfile = context.tmpBase + ".out"
98
timefile = context.tmpBase + ".time"
109
config = context.config
1110
cmd = shellcommand.parse(commandline)
@@ -34,6 +33,7 @@ def _mutateCommandLine(context, commandline):
3433
if cmd.stdout is not None or cmd.stderr is not None:
3534
raise Exception("Separate stdout/stderr redirection not " +
3635
"possible with traditional output")
36+
outfile = context.tmpBase + ".out"
3737
args += ["--append-exitstatus"]
3838
args += ["--redirect-output", outfile]
3939
stdin = cmd.stdin
@@ -64,7 +64,8 @@ def _mutateScript(context, script):
6464
def _collectTime(context, timefiles, metric_name='exec_time'):
6565
time = 0.0
6666
for timefile in timefiles:
67-
time += getUserTime(timefile)
67+
filecontent = context.read_result_file(context, timefile)
68+
time += getUserTimeFromContents(filecontent)
6869
return {metric_name: time}
6970

7071

@@ -79,9 +80,14 @@ def mutatePlan(context, plan):
7980

8081

8182
def getUserTime(filename):
82-
"""Extract the user time form a .time file produced by timeit"""
83+
"""Extract the user time from a .time file produced by timeit"""
8384
with open(filename) as fd:
84-
line = [line for line in fd.readlines() if line.startswith('user')]
85+
contents = fd.read()
86+
return getUserTimeFromContents(contents)
87+
88+
89+
def getUserTimeFromContents(contents):
90+
line = [line for line in contents.splitlines() if line.startswith('user')]
8591
assert len(line) == 1
8692

8793
m = re.match(r'user\s+([0-9.]+)', line[0])

litsupport/test.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,6 @@
1717
NOEXE = lit.Test.ResultCode('NOEXE', True)
1818

1919

20-
class TestContext:
21-
"""This class is used to hold data used while constructing a testrun.
22-
For example this can be used by modules modifying the commandline with
23-
extra instrumentation/measurement wrappers to pass the filenames of the
24-
results to a final data collection step."""
25-
def __init__(self, test, litConfig, tmpDir, tmpBase):
26-
self.test = test
27-
self.config = test.config
28-
self.litConfig = litConfig
29-
self.tmpDir = tmpDir
30-
self.tmpBase = tmpBase
31-
32-
3320
class TestSuiteTest(lit.formats.ShTest):
3421
def __init__(self):
3522
super(TestSuiteTest, self).__init__()
@@ -44,7 +31,8 @@ def execute(self, test, litConfig):
4431
# Parse .test file and initialize context
4532
tmpDir, tmpBase = lit.TestRunner.getTempPaths(test)
4633
lit.util.mkdir_p(os.path.dirname(tmpBase))
47-
context = TestContext(test, litConfig, tmpDir, tmpBase)
34+
context = litsupport.testplan.TestContext(test, litConfig, tmpDir,
35+
tmpBase)
4836
litsupport.testfile.parse(context, test.getSourcePath())
4937
plan = litsupport.testplan.TestPlan()
5038

litsupport/testplan.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def __init__(self):
2121
self.metricscripts = {}
2222
self.metric_collectors = []
2323
self.preparescript = []
24+
self.profile_files = []
2425
self.profilescript = []
2526

2627

@@ -169,3 +170,22 @@ def check_call(commandline, *aargs, **dargs):
169170
"""Wrapper around subprocess.check_call that logs the command."""
170171
logging.info(" ".join(commandline))
171172
return subprocess.check_call(commandline, *aargs, **dargs)
173+
174+
175+
def default_read_result_file(context, path):
176+
with open(path) as fd:
177+
return fd.read()
178+
179+
180+
class TestContext:
181+
"""This class is used to hold data used while constructing a testrun.
182+
For example this can be used by modules modifying the commandline with
183+
extra instrumentation/measurement wrappers to pass the filenames of the
184+
results to a final data collection step."""
185+
def __init__(self, test, litConfig, tmpDir, tmpBase):
186+
self.test = test
187+
self.config = test.config
188+
self.litConfig = litConfig
189+
self.tmpDir = tmpDir
190+
self.tmpBase = tmpBase
191+
self.read_result_file = default_read_result_file

tools/CMakeLists.txt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
# Note that we have to compile fpcmp and timeit for the host machine even when
2-
# cross compiling to a different target. We use custom rules doing a simple
3-
# "cc file.c".
1+
# Tools for compiling and running the benchmarks.
2+
#
3+
# Note: Tools used while running the benchmark should be (cross-)compiled
4+
# normally while tools used for building the benchmark need to be built for
5+
# the host system (even when cross-compiling the benchmark) with
6+
# `llvm_add_host_executable`.
47

58
include(Host)
69

7-
llvm_add_host_executable(build-fpcmp fpcmp fpcmp.c)
10+
add_executable(fpcmp fpcmp.c)
811

912
add_custom_command(
1013
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/HashProgramOutput.sh

utils/rsync.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
# Sync a build directory to remote device for running.
3+
set -eu
4+
DEVICE="$1"
5+
BUILDDIR="$2"
6+
7+
case $BUILDDIR in
8+
/*) ;;
9+
*)
10+
echo 1>&2 "Builddir path must be absolute!"
11+
exit 1
12+
;;
13+
esac
14+
15+
RSYNC_FLAGS=""
16+
RSYNC_FLAGS+=" -a"
17+
RSYNC_FLAGS+=" --delete --delete-excluded"
18+
# We cannot easily differentiate between intermediate build results and
19+
# files necessary to run the benchmark, so for now we just exclude based on
20+
# some file extensions...
21+
RSYNC_FLAGS+=" --exclude=\"*.o\""
22+
RSYNC_FLAGS+=" --exclude=\"*.a\""
23+
RSYNC_FLAGS+=" --exclude=\"*.time\""
24+
RSYNC_FLAGS+=" --exclude=\"*.cmake\""
25+
RSYNC_FLAGS+=" --exclude=Output/"
26+
RSYNC_FLAGS+=" --exclude=.ninja_deps"
27+
RSYNC_FLAGS+=" --exclude=.ninja_log"
28+
RSYNC_FLAGS+=" --exclude=build.ninja"
29+
RSYNC_FLAGS+=" --exclude=rules.ninja"
30+
RSYNC_FLAGS+=" --exclude=CMakeFiles/"
31+
32+
set -x
33+
ssh $DEVICE mkdir -p "$BUILDDIR"
34+
eval rsync $RSYNC_FLAGS $BUILDDIR/ $DEVICE:$BUILDDIR/

0 commit comments

Comments
 (0)