Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Linux kmemleak support to ZTS #13084

Merged
merged 1 commit into from
Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions scripts/zfs-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ ZFS_DBGMSG="$STF_SUITE/callbacks/zfs_dbgmsg.ksh"
ZFS_DMESG="$STF_SUITE/callbacks/zfs_dmesg.ksh"
UNAME=$(uname -s)
RERUN=""
KMEMLEAK=""

# Override some defaults if on FreeBSD
if [ "$UNAME" = "FreeBSD" ] ; then
Expand Down Expand Up @@ -329,6 +330,7 @@ OPTIONS:
-S Enable stack tracer (negative performance impact)
-c Only create and populate constrained path
-R Automatically rerun failing tests
-m Enable kmemleak reporting (Linux only)
-n NFSFILE Use the nfsfile to determine the NFS configuration
-I NUM Number of iterations
-d DIR Use DIR for files and loopback devices
Expand All @@ -355,7 +357,7 @@ $0 -x
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
h)
usage
Expand Down Expand Up @@ -386,6 +388,9 @@ while getopts 'hvqxkfScRn:d:s:r:?t:T:u:I:' OPTION; do
R)
RERUN="yes"
;;
m)
KMEMLEAK="yes"
;;
n)
nfsfile=$OPTARG
[ -f "$nfsfile" ] || fail "Cannot read file: $nfsfile"
Expand Down Expand Up @@ -695,12 +700,16 @@ fi
#
# Run all the tests as specified.
#
msg "${TEST_RUNNER} ${QUIET:+-q}" \
msg "${TEST_RUNNER}" \
"${QUIET:+-q}" \
"${KMEMLEAK:+-m}" \
"-c \"${RUNFILES}\"" \
"-T \"${TAGS}\"" \
"-i \"${STF_SUITE}\"" \
"-I \"${ITERATIONS}\""
{ ${TEST_RUNNER} ${QUIET:+-q} \
{ ${TEST_RUNNER} \
${QUIET:+-q} \
${KMEMLEAK:+-m} \
-c "${RUNFILES}" \
-T "${TAGS}" \
-i "${STF_SUITE}" \
Expand All @@ -722,7 +731,9 @@ if [ "$RESULT" -eq "2" ] && [ -n "$RERUN" ]; then
for test_name in $MAYBES; do
grep "$test_name " "$TEMP_RESULTS_FILE" >>"$TEST_LIST"
done
{ ${TEST_RUNNER} ${QUIET:+-q} \
{ ${TEST_RUNNER} \
${QUIET:+-q} \
${KMEMLEAK:+-m} \
-c "${RUNFILES}" \
-T "${TAGS}" \
-i "${STF_SUITE}" \
Expand Down
54 changes: 45 additions & 9 deletions tests/test-runner/bin/test-runner.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ from pwd import getpwuid
from select import select
from subprocess import PIPE
from subprocess import Popen
from subprocess import check_output
from threading import Timer
from time import time, CLOCK_MONOTONIC

BASEDIR = '/var/tmp/test_results'
TESTDIR = '/usr/share/zfs/'
KMEMLEAK_FILE = '/sys/kernel/debug/kmemleak'
KILL = 'kill'
TRUE = 'true'
SUDO = 'sudo'
Expand Down Expand Up @@ -75,6 +77,7 @@ class Result(object):
self.runtime = ''
self.stdout = []
self.stderr = []
self.kmemleak = ''
self.result = ''

def done(self, proc, killed, reran):
Expand All @@ -90,6 +93,9 @@ class Result(object):
if killed:
self.result = 'KILLED'
Result.runresults['KILLED'] += 1
elif len(self.kmemleak) > 0:
self.result = 'FAIL'
Result.runresults['FAIL'] += 1
elif self.returncode == 0:
self.result = 'PASS'
Result.runresults['PASS'] += 1
Expand Down Expand Up @@ -250,7 +256,7 @@ User: %s

return out.lines, err.lines

def run(self, dryrun):
def run(self, dryrun, kmemleak):
"""
This is the main function that runs each individual test.
Determine whether or not the command requires sudo, and modify it
Expand All @@ -270,6 +276,11 @@ User: %s
fail('%s' % e)

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)
# Allow a special timeout value of 0 to mean infinity
if int(self.timeout) == 0:
Expand All @@ -279,6 +290,12 @@ User: %s
try:
t.start()
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:
self.kill_cmd(proc, True)
fail('\nRun terminated at user request.')
Expand Down Expand Up @@ -355,6 +372,9 @@ User: %s
with open(os.path.join(self.outputdir, 'merged'), 'wb') as merged:
for _, line in lines:
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):
Expand Down Expand Up @@ -439,22 +459,22 @@ Tags: %s

cont = True
if len(pretest.pathname):
pretest.run(options.dryrun)
pretest.run(options.dryrun, False)
cont = pretest.result.result == 'PASS'
pretest.log(options)

if cont:
test.run(options.dryrun)
test.run(options.dryrun, options.kmemleak)
if test.result.result == 'KILLED' and len(failsafe.pathname):
failsafe.run(options.dryrun)
failsafe.run(options.dryrun, False)
failsafe.log(options, suppress_console=True)
else:
test.skip()

test.log(options)

if len(posttest.pathname):
posttest.run(options.dryrun)
posttest.run(options.dryrun, False)
posttest.log(options)


Expand Down Expand Up @@ -557,7 +577,7 @@ Tags: %s

cont = True
if len(pretest.pathname):
pretest.run(options.dryrun)
pretest.run(options.dryrun, False)
cont = pretest.result.result == 'PASS'
pretest.log(options)

Expand All @@ -570,17 +590,17 @@ Tags: %s
failsafe = Cmd(self.failsafe, outputdir=odir, timeout=self.timeout,
user=self.failsafe_user, identifier=self.identifier)
if cont:
test.run(options.dryrun)
test.run(options.dryrun, options.kmemleak)
if test.result.result == 'KILLED' and len(failsafe.pathname):
failsafe.run(options.dryrun)
failsafe.run(options.dryrun, False)
failsafe.log(options, suppress_console=True)
else:
test.skip()

test.log(options)

if len(posttest.pathname):
posttest.run(options.dryrun)
posttest.run(options.dryrun, False)
posttest.log(options)


Expand Down Expand Up @@ -845,6 +865,11 @@ class TestRun(object):
else:
write_log('Could not make a symlink to directory %s\n' %
self.outputdir, LOG_ERR)

if options.kmemleak:
cmd = f'echo scan=0 | {SUDO} tee {KMEMLEAK_FILE}'
check_output(cmd, shell=True)

iteration = 0
while iteration < options.iterations:
for test in sorted(self.tests.keys()):
Expand Down Expand Up @@ -990,6 +1015,14 @@ def fail(retstr, ret=1):
exit(ret)


def kmemleak_cb(option, opt_str, value, parser):
if not os.path.exists(KMEMLEAK_FILE):
Copy link
Contributor

@samwyc samwyc Apr 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check fails on fedora-based systems for non-root users, as permission to /sys/kernel/debug/kmemleak is denied.
Hence we're not able to enable the feature, even if CONFIG_DEBUG_KMEMLEAK is set.

[rocky@rocky-kernel ~]$ cat /sys/kernel/debug/kmemleak
cat: /sys/kernel/debug/kmemleak: Permission denied
[rocky@rocky-kernel ~]$ grep "CONFIG_DEBUG_KMEMLEAK" /boot/config-4.18.0+
CONFIG_DEBUG_KMEMLEAK=y
[rocky@rocky-kernel ~]$ python3 -c "import os;print(os.path.exists('/sys/kernel/debug/kmemleak'))"
False

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):
path_options = ['outputdir', 'template', 'testdir', 'logfile']

Expand Down Expand Up @@ -1027,6 +1060,9 @@ def parse_args():
parser.add_option('-i', action='callback', callback=options_cb,
default=TESTDIR, dest='testdir', type='string',
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,
default='', dest='pre', metavar='script',
type='string', help='Specify a pre script.')
Expand Down
2 changes: 2 additions & 0 deletions tests/test-runner/man/test-runner.1
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ to be consumed by the run command.
.It Fl d
Dry run mode.
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
Create test groups from any directories found while searching for tests.
.It Fl o Ar outputdir
Expand Down