Add Linux kmemleak support to ZTS
- Kmemleak `clear` is invoked right before every test case run. - Kmemleak `scan` is requested right after each test case is finished. - Kmemleak instrumentation is not used for setup/cleanup/pretest/posttest/failsafe stages to shorten the test case execution time. - Kmemleak periodic scan is disabled (`scan=0`) before the test suite run to avoid interfering with the on-demand scan results. - There are unavoidable potential false positives coming from kernel areas other than OpenZFS module. - The ZTS with kmemleak enabled duration is increased by ~50%. Example run ``` Running Time: 07:12:13 Percent passed: 98.3% unreferenced object 0xffff9da82aea5410 (size 80): comm "kworker/u32:10", pid 942206, jiffies 4296749716 (age 2615.516s) hex dump (first 32 bytes): 00 30 30 00 00 00 00 00 ff 8f 30 00 00 00 00 00 .00.......0..... 51 e6 77 05 a8 9d ff ff 00 00 00 00 00 00 00 00 Q.w............. backtrace: [<000000005cf1fea2>] alloc_extent_state+0x1d/0xb0 [btrfs] [<0000000083f78ae5>] set_extent_bit+0x2ff/0x670 [btrfs] [<00000000de29249e>] lock_extent_bits+0x6b/0xa0 [btrfs] [<00000000b241f424>] lock_and_cleanup_extent_if_need+0xaf/0x1c0 [btrfs] [<0000000093ca72b5>] btrfs_buffered_write+0x297/0x7d0 [btrfs] [<000000002c2938c8>] btrfs_file_write_iter+0x127/0x390 [btrfs] [<00000000b888f720>] do_iter_readv_writev+0x152/0x1b0 [<00000000320f0bcc>] do_iter_write+0x7c/0x1c0 [<000000000b5a8fe0>] lo_write_bvec+0x62/0x150 [loop] [<000000009aa03c73>] loop_process_work+0x250/0xbd0 [loop] [<00000000c7487d8a>] process_one_work+0x1f1/0x390 [<000000000b236831>] worker_thread+0x53/0x3e0 [<0000000023cb3e57>] kthread+0x127/0x150 [<000000002d48676a>] ret_from_fork+0x22/0x30 ``` Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Ryan Moeller <ryan@iXsystems.com> Signed-off-by: szubersk <szuberskidamian@gmail.com> Closes #13084
This commit is contained in:
parent
a164143dfd
commit
a69765ea5b
|
@ -53,6 +53,7 @@ ZFS_DBGMSG="$STF_SUITE/callbacks/zfs_dbgmsg.ksh"
|
||||||
ZFS_DMESG="$STF_SUITE/callbacks/zfs_dmesg.ksh"
|
ZFS_DMESG="$STF_SUITE/callbacks/zfs_dmesg.ksh"
|
||||||
UNAME=$(uname -s)
|
UNAME=$(uname -s)
|
||||||
RERUN=""
|
RERUN=""
|
||||||
|
KMEMLEAK=""
|
||||||
|
|
||||||
# Override some defaults if on FreeBSD
|
# Override some defaults if on FreeBSD
|
||||||
if [ "$UNAME" = "FreeBSD" ] ; then
|
if [ "$UNAME" = "FreeBSD" ] ; then
|
||||||
|
@ -328,6 +329,7 @@ OPTIONS:
|
||||||
-S Enable stack tracer (negative performance impact)
|
-S Enable stack tracer (negative performance impact)
|
||||||
-c Only create and populate constrained path
|
-c Only create and populate constrained path
|
||||||
-R Automatically rerun failing tests
|
-R Automatically rerun failing tests
|
||||||
|
-m Enable kmemleak reporting (Linux only)
|
||||||
-n NFSFILE Use the nfsfile to determine the NFS configuration
|
-n NFSFILE Use the nfsfile to determine the NFS configuration
|
||||||
-I NUM Number of iterations
|
-I NUM Number of iterations
|
||||||
-d DIR Use DIR for files and loopback devices
|
-d DIR Use DIR for files and loopback devices
|
||||||
|
@ -354,7 +356,7 @@ $0 -x
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
while getopts 'hvqxkfScRn:d:s:r:?t:T:u:I:' OPTION; do
|
while getopts 'hvqxkfScRmn:d:s:r:?t:T:u:I:' OPTION; do
|
||||||
case $OPTION in
|
case $OPTION in
|
||||||
h)
|
h)
|
||||||
usage
|
usage
|
||||||
|
@ -385,6 +387,9 @@ while getopts 'hvqxkfScRn:d:s:r:?t:T:u:I:' OPTION; do
|
||||||
R)
|
R)
|
||||||
RERUN="yes"
|
RERUN="yes"
|
||||||
;;
|
;;
|
||||||
|
m)
|
||||||
|
KMEMLEAK="yes"
|
||||||
|
;;
|
||||||
n)
|
n)
|
||||||
nfsfile=$OPTARG
|
nfsfile=$OPTARG
|
||||||
[ -f "$nfsfile" ] || fail "Cannot read file: $nfsfile"
|
[ -f "$nfsfile" ] || fail "Cannot read file: $nfsfile"
|
||||||
|
@ -694,12 +699,14 @@ REPORT_FILE=$(mktemp_file zts-report)
|
||||||
#
|
#
|
||||||
# Run all the tests as specified.
|
# Run all the tests as specified.
|
||||||
#
|
#
|
||||||
msg "${TEST_RUNNER} ${QUIET:+-q}" \
|
msg "${TEST_RUNNER}" \
|
||||||
|
"${QUIET:+-q}" \
|
||||||
|
"${KMEMLEAK:+-m}" \
|
||||||
"-c \"${RUNFILES}\"" \
|
"-c \"${RUNFILES}\"" \
|
||||||
"-T \"${TAGS}\"" \
|
"-T \"${TAGS}\"" \
|
||||||
"-i \"${STF_SUITE}\"" \
|
"-i \"${STF_SUITE}\"" \
|
||||||
"-I \"${ITERATIONS}\""
|
"-I \"${ITERATIONS}\""
|
||||||
${TEST_RUNNER} ${QUIET:+-q} \
|
${TEST_RUNNER} ${QUIET:+-q} ${KMEMLEAK:+-m} \
|
||||||
-c "${RUNFILES}" \
|
-c "${RUNFILES}" \
|
||||||
-T "${TAGS}" \
|
-T "${TAGS}" \
|
||||||
-i "${STF_SUITE}" \
|
-i "${STF_SUITE}" \
|
||||||
|
@ -719,7 +726,7 @@ if [ "$RESULT" -eq "2" ] && [ -n "$RERUN" ]; then
|
||||||
for test_name in $MAYBES; do
|
for test_name in $MAYBES; do
|
||||||
grep "$test_name " "$TEMP_RESULTS_FILE" >>"$TEST_LIST"
|
grep "$test_name " "$TEMP_RESULTS_FILE" >>"$TEST_LIST"
|
||||||
done
|
done
|
||||||
${TEST_RUNNER} ${QUIET:+-q} \
|
${TEST_RUNNER} ${QUIET:+-q} ${KMEMLEAK:+-m} \
|
||||||
-c "${RUNFILES}" \
|
-c "${RUNFILES}" \
|
||||||
-T "${TAGS}" \
|
-T "${TAGS}" \
|
||||||
-i "${STF_SUITE}" \
|
-i "${STF_SUITE}" \
|
||||||
|
|
|
@ -36,11 +36,13 @@ from pwd import getpwuid
|
||||||
from select import select
|
from select import select
|
||||||
from subprocess import PIPE
|
from subprocess import PIPE
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
from subprocess import check_output
|
||||||
from threading import Timer
|
from threading import Timer
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
BASEDIR = '/var/tmp/test_results'
|
BASEDIR = '/var/tmp/test_results'
|
||||||
TESTDIR = '/usr/share/zfs/'
|
TESTDIR = '/usr/share/zfs/'
|
||||||
|
KMEMLEAK_FILE = '/sys/kernel/debug/kmemleak'
|
||||||
KILL = 'kill'
|
KILL = 'kill'
|
||||||
TRUE = 'true'
|
TRUE = 'true'
|
||||||
SUDO = 'sudo'
|
SUDO = 'sudo'
|
||||||
|
@ -83,6 +85,7 @@ class Result(object):
|
||||||
self.runtime = ''
|
self.runtime = ''
|
||||||
self.stdout = []
|
self.stdout = []
|
||||||
self.stderr = []
|
self.stderr = []
|
||||||
|
self.kmemleak = ''
|
||||||
self.result = ''
|
self.result = ''
|
||||||
|
|
||||||
def done(self, proc, killed, reran):
|
def done(self, proc, killed, reran):
|
||||||
|
@ -98,6 +101,9 @@ class Result(object):
|
||||||
if killed:
|
if killed:
|
||||||
self.result = 'KILLED'
|
self.result = 'KILLED'
|
||||||
Result.runresults['KILLED'] += 1
|
Result.runresults['KILLED'] += 1
|
||||||
|
elif len(self.kmemleak) > 0:
|
||||||
|
self.result = 'FAIL'
|
||||||
|
Result.runresults['FAIL'] += 1
|
||||||
elif self.returncode == 0:
|
elif self.returncode == 0:
|
||||||
self.result = 'PASS'
|
self.result = 'PASS'
|
||||||
Result.runresults['PASS'] += 1
|
Result.runresults['PASS'] += 1
|
||||||
|
@ -258,7 +264,7 @@ User: %s
|
||||||
|
|
||||||
return out.lines, err.lines
|
return out.lines, err.lines
|
||||||
|
|
||||||
def run(self, dryrun):
|
def run(self, dryrun, kmemleak):
|
||||||
"""
|
"""
|
||||||
This is the main function that runs each individual test.
|
This is the main function that runs each individual test.
|
||||||
Determine whether or not the command requires sudo, and modify it
|
Determine whether or not the command requires sudo, and modify it
|
||||||
|
@ -278,6 +284,11 @@ User: %s
|
||||||
fail('%s' % e)
|
fail('%s' % e)
|
||||||
|
|
||||||
self.result.starttime = monotonic_time()
|
self.result.starttime = monotonic_time()
|
||||||
|
|
||||||
|
if kmemleak:
|
||||||
|
cmd = f'echo clear | {SUDO} tee {KMEMLEAK_FILE}'
|
||||||
|
check_output(cmd, shell=True)
|
||||||
|
|
||||||
proc = Popen(privcmd, stdout=PIPE, stderr=PIPE)
|
proc = Popen(privcmd, stdout=PIPE, stderr=PIPE)
|
||||||
# Allow a special timeout value of 0 to mean infinity
|
# Allow a special timeout value of 0 to mean infinity
|
||||||
if int(self.timeout) == 0:
|
if int(self.timeout) == 0:
|
||||||
|
@ -287,6 +298,12 @@ User: %s
|
||||||
try:
|
try:
|
||||||
t.start()
|
t.start()
|
||||||
self.result.stdout, self.result.stderr = self.collect_output(proc)
|
self.result.stdout, self.result.stderr = self.collect_output(proc)
|
||||||
|
|
||||||
|
if kmemleak:
|
||||||
|
cmd = f'echo scan | {SUDO} tee {KMEMLEAK_FILE}'
|
||||||
|
check_output(cmd, shell=True)
|
||||||
|
cmd = f'{SUDO} cat {KMEMLEAK_FILE}'
|
||||||
|
self.result.kmemleak = check_output(cmd, shell=True)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.kill_cmd(proc, True)
|
self.kill_cmd(proc, True)
|
||||||
fail('\nRun terminated at user request.')
|
fail('\nRun terminated at user request.')
|
||||||
|
@ -363,6 +380,9 @@ User: %s
|
||||||
with open(os.path.join(self.outputdir, 'merged'), 'wb') as merged:
|
with open(os.path.join(self.outputdir, 'merged'), 'wb') as merged:
|
||||||
for _, line in lines:
|
for _, line in lines:
|
||||||
os.write(merged.fileno(), b'%s\n' % line)
|
os.write(merged.fileno(), b'%s\n' % line)
|
||||||
|
if len(self.result.kmemleak):
|
||||||
|
with open(os.path.join(self.outputdir, 'kmemleak'), 'wb') as kmem:
|
||||||
|
kmem.write(self.result.kmemleak)
|
||||||
|
|
||||||
|
|
||||||
class Test(Cmd):
|
class Test(Cmd):
|
||||||
|
@ -447,14 +467,14 @@ Tags: %s
|
||||||
|
|
||||||
cont = True
|
cont = True
|
||||||
if len(pretest.pathname):
|
if len(pretest.pathname):
|
||||||
pretest.run(options.dryrun)
|
pretest.run(options.dryrun, False)
|
||||||
cont = pretest.result.result == 'PASS'
|
cont = pretest.result.result == 'PASS'
|
||||||
pretest.log(options)
|
pretest.log(options)
|
||||||
|
|
||||||
if cont:
|
if cont:
|
||||||
test.run(options.dryrun)
|
test.run(options.dryrun, options.kmemleak)
|
||||||
if test.result.result == 'KILLED' and len(failsafe.pathname):
|
if test.result.result == 'KILLED' and len(failsafe.pathname):
|
||||||
failsafe.run(options.dryrun)
|
failsafe.run(options.dryrun, False)
|
||||||
failsafe.log(options, suppress_console=True)
|
failsafe.log(options, suppress_console=True)
|
||||||
else:
|
else:
|
||||||
test.skip()
|
test.skip()
|
||||||
|
@ -462,7 +482,7 @@ Tags: %s
|
||||||
test.log(options)
|
test.log(options)
|
||||||
|
|
||||||
if len(posttest.pathname):
|
if len(posttest.pathname):
|
||||||
posttest.run(options.dryrun)
|
posttest.run(options.dryrun, False)
|
||||||
posttest.log(options)
|
posttest.log(options)
|
||||||
|
|
||||||
|
|
||||||
|
@ -565,7 +585,7 @@ Tags: %s
|
||||||
|
|
||||||
cont = True
|
cont = True
|
||||||
if len(pretest.pathname):
|
if len(pretest.pathname):
|
||||||
pretest.run(options.dryrun)
|
pretest.run(options.dryrun, False)
|
||||||
cont = pretest.result.result == 'PASS'
|
cont = pretest.result.result == 'PASS'
|
||||||
pretest.log(options)
|
pretest.log(options)
|
||||||
|
|
||||||
|
@ -578,9 +598,9 @@ Tags: %s
|
||||||
failsafe = Cmd(self.failsafe, outputdir=odir, timeout=self.timeout,
|
failsafe = Cmd(self.failsafe, outputdir=odir, timeout=self.timeout,
|
||||||
user=self.failsafe_user, identifier=self.identifier)
|
user=self.failsafe_user, identifier=self.identifier)
|
||||||
if cont:
|
if cont:
|
||||||
test.run(options.dryrun)
|
test.run(options.dryrun, options.kmemleak)
|
||||||
if test.result.result == 'KILLED' and len(failsafe.pathname):
|
if test.result.result == 'KILLED' and len(failsafe.pathname):
|
||||||
failsafe.run(options.dryrun)
|
failsafe.run(options.dryrun, False)
|
||||||
failsafe.log(options, suppress_console=True)
|
failsafe.log(options, suppress_console=True)
|
||||||
else:
|
else:
|
||||||
test.skip()
|
test.skip()
|
||||||
|
@ -588,7 +608,7 @@ Tags: %s
|
||||||
test.log(options)
|
test.log(options)
|
||||||
|
|
||||||
if len(posttest.pathname):
|
if len(posttest.pathname):
|
||||||
posttest.run(options.dryrun)
|
posttest.run(options.dryrun, False)
|
||||||
posttest.log(options)
|
posttest.log(options)
|
||||||
|
|
||||||
|
|
||||||
|
@ -853,6 +873,11 @@ class TestRun(object):
|
||||||
else:
|
else:
|
||||||
write_log('Could not make a symlink to directory %s\n' %
|
write_log('Could not make a symlink to directory %s\n' %
|
||||||
self.outputdir, LOG_ERR)
|
self.outputdir, LOG_ERR)
|
||||||
|
|
||||||
|
if options.kmemleak:
|
||||||
|
cmd = f'echo scan=0 | {SUDO} tee {KMEMLEAK_FILE}'
|
||||||
|
check_output(cmd, shell=True)
|
||||||
|
|
||||||
iteration = 0
|
iteration = 0
|
||||||
while iteration < options.iterations:
|
while iteration < options.iterations:
|
||||||
for test in sorted(self.tests.keys()):
|
for test in sorted(self.tests.keys()):
|
||||||
|
@ -998,6 +1023,14 @@ def fail(retstr, ret=1):
|
||||||
exit(ret)
|
exit(ret)
|
||||||
|
|
||||||
|
|
||||||
|
def kmemleak_cb(option, opt_str, value, parser):
|
||||||
|
if not os.path.exists(KMEMLEAK_FILE):
|
||||||
|
fail(f"File '{KMEMLEAK_FILE}' doesn't exist. " +
|
||||||
|
"Enable CONFIG_DEBUG_KMEMLEAK in kernel configuration.")
|
||||||
|
|
||||||
|
setattr(parser.values, option.dest, True)
|
||||||
|
|
||||||
|
|
||||||
def options_cb(option, opt_str, value, parser):
|
def options_cb(option, opt_str, value, parser):
|
||||||
path_options = ['outputdir', 'template', 'testdir', 'logfile']
|
path_options = ['outputdir', 'template', 'testdir', 'logfile']
|
||||||
|
|
||||||
|
@ -1035,6 +1068,9 @@ def parse_args():
|
||||||
parser.add_option('-i', action='callback', callback=options_cb,
|
parser.add_option('-i', action='callback', callback=options_cb,
|
||||||
default=TESTDIR, dest='testdir', type='string',
|
default=TESTDIR, dest='testdir', type='string',
|
||||||
metavar='testdir', help='Specify a test directory.')
|
metavar='testdir', help='Specify a test directory.')
|
||||||
|
parser.add_option('-m', action='callback', callback=kmemleak_cb,
|
||||||
|
default=False, dest='kmemleak',
|
||||||
|
help='Enable kmemleak reporting (Linux only)')
|
||||||
parser.add_option('-p', action='callback', callback=options_cb,
|
parser.add_option('-p', action='callback', callback=options_cb,
|
||||||
default='', dest='pre', metavar='script',
|
default='', dest='pre', metavar='script',
|
||||||
type='string', help='Specify a pre script.')
|
type='string', help='Specify a pre script.')
|
||||||
|
|
|
@ -210,6 +210,8 @@ to be consumed by the run command.
|
||||||
.It Fl d
|
.It Fl d
|
||||||
Dry run mode.
|
Dry run mode.
|
||||||
Execute no tests, but print a description of each test that would have been run.
|
Execute no tests, but print a description of each test that would have been run.
|
||||||
|
.It Fl m
|
||||||
|
Enable kmemleak reporting (Linux only)
|
||||||
.It Fl g
|
.It Fl g
|
||||||
Create test groups from any directories found while searching for tests.
|
Create test groups from any directories found while searching for tests.
|
||||||
.It Fl o Ar outputdir
|
.It Fl o Ar outputdir
|
||||||
|
|
Loading…
Reference in New Issue