diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 75f4ab23a21f..db2e08af424e 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -64,3 +64,5 @@ add_test(NAME py_test_percpu WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_percpu sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_percpu.py) add_test(NAME py_test_dump_func WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_dump_func simple ${CMAKE_CURRENT_SOURCE_DIR}/test_dump_func.py) +add_test(NAME py_test_tools_smoke WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${TEST_WRAPPER} py_test_tools_smoke sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_tools_smoke.py) diff --git a/tests/python/test_tools_smoke.py b/tests/python/test_tools_smoke.py new file mode 100755 index 000000000000..4502ae7f68ff --- /dev/null +++ b/tests/python/test_tools_smoke.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python +# Copyright (c) Sasha Goldshtein, 2017 +# Licensed under the Apache License, Version 2.0 (the "License") + +import distutils.version +import subprocess +import os +from unittest import main, skipUnless, TestCase + +TOOLS_DIR = "../../tools/" + +def kernel_version_ge(major, minor): + # True if running kernel is >= X.Y + version = distutils.version.LooseVersion(os.uname()[2]).version + if version[0] > major: + return True + if version[0] < major: + return False + if minor and version[1] < minor: + return False + return True + +@skipUnless(kernel_version_ge(4,1), "requires kernel >= 4.1") +class SmokeTests(TestCase): + # Use this for commands that have a built-in timeout, so they only need + # to be killed in case of a hard hang. + def run_with_duration(self, command, timeout=10): + full_command = TOOLS_DIR + command + self.assertEqual(0, # clean exit + subprocess.call("timeout -s KILL %ds %s > /dev/null" % + (timeout, full_command), shell=True)) + + # Use this for commands that don't have a built-in timeout, so we have + # to Ctrl-C out of them by sending SIGINT. If that still doesn't stop + # them, send a kill signal 5 seconds later. + def run_with_int(self, command, timeout=5, kill_timeout=5, + allow_early=False, kill=False): + full_command = TOOLS_DIR + command + signal = "KILL" if kill else "INT" + rc = subprocess.call("timeout -s %s -k %ds %ds %s > /dev/null" % + (signal, kill_timeout, timeout, full_command), shell=True) + # timeout returns 124 if the program did not terminate prematurely, + # and returns 137 if we used KILL instead of INT. So there are three + # sensible scenarios: + # 1. The script is allowed to return early, and it did, with a + # success return code. + # 2. The script timed out and was killed by the SIGINT signal. + # 3. The script timed out and was killed by the SIGKILL signal, and + # this was what we asked for using kill=True. + self.assertTrue((rc == 0 and allow_early) or rc == 124 + or (rc == 137 and kill), "rc was %d" % rc) + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_argdist(self): + self.run_with_duration("argdist.py -C 'p::SyS_open()' -n 1 -i 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_bashreadline(self): + self.run_with_int("bashreadline.py") + + def test_biolatency(self): + self.run_with_duration("biolatency.py 1 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_biosnoop(self): + self.run_with_int("biosnoop.py") + + def test_biotop(self): + self.run_with_duration("biotop.py 1 1") + + def test_bitesize(self): + self.run_with_int("biotop.py") + + def test_btrfsdist(self): + # Will attempt to do anything meaningful only when btrfs is installed. + self.run_with_duration("btrfsdist.py 1 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_btrfsslower(self): + # Will attempt to do anything meaningful only when btrfs is installed. + self.run_with_int("btrfsslower.py", allow_early=True) + + def test_cachestat(self): + self.run_with_duration("cachestat.py 1 1") + + def test_cachetop(self): + # TODO cachetop doesn't like to run without a terminal, disabled + # for now. + # self.run_with_int("cachetop.py 1") + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_capable(self): + self.run_with_int("capable.py") + + def test_cpudist(self): + self.run_with_duration("cpudist.py 1 1") + + @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") + def test_cpuunclaimed(self): + self.run_with_duration("cpuunclaimed.py 1 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_dbslower(self): + # Deliberately left empty -- dbslower requires an instance of either + # MySQL or PostgreSQL to be running, or it fails to attach. + pass + + @skipUnless(kernel_version_ge(4,3), "requires kernel >= 4.3") + def test_dbstat(self): + # Deliberately left empty -- dbstat requires an instance of either + # MySQL or PostgreSQL to be running, or it fails to attach. + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_dcsnoop(self): + self.run_with_int("dcsnoop.py") + + def test_dcstat(self): + self.run_with_duration("dcstat.py 1 1") + + @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") + def test_deadlock_detector(self): + # TODO This tool requires a massive BPF stack traces table allocation, + # which might fail the run or even trigger the oomkiller to kill some + # other processes. Disabling for now. + # self.run_with_int("deadlock_detector.py $(pgrep -n bash)", timeout=10) + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_execsnoop(self): + self.run_with_int("execsnoop.py") + + def test_ext4dist(self): + self.run_with_duration("ext4dist.py 1 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_ext4slower(self): + self.run_with_int("ext4slower.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_filelife(self): + self.run_with_int("filelife.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_fileslower(self): + self.run_with_int("fileslower.py") + + def test_filetop(self): + self.run_with_duration("filetop.py 1 1") + + def test_funccount(self): + self.run_with_int("funccount.py __kmalloc -i 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_funclatency(self): + self.run_with_int("funclatency.py __kmalloc -i 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_gethostlatency(self): + self.run_with_int("gethostlatency.py") + + def test_hardirqs(self): + self.run_with_duration("hardirqs.py 1 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_killsnoop(self): + # Because killsnoop intercepts signals, if we send it a SIGINT we we + # we likely catch it while it is handling the data packet from the + # BPF program, and the exception from the SIGINT will be swallowed by + # ctypes. Therefore, we use SIGKILL. + # To reproduce the above issue, run killsnoop and in another shell run + # `kill -s SIGINT $(pidof python)`. As a result, killsnoop will print + # a traceback but will not exit. + self.run_with_int("killsnoop.py", kill=True) + + @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") + def test_llcstat(self): + # Requires PMU, which is not available in virtual machines. + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_mdflush(self): + self.run_with_int("mdflush.py") + + @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") + def test_memleak(self): + self.run_with_duration("memleak.py 1 1") + + @skipUnless(kernel_version_ge(4,8), "requires kernel >= 4.8") + def test_mountsnoop(self): + self.run_with_int("mountsnoop.py") + + @skipUnless(kernel_version_ge(4,3), "requires kernel >= 4.3") + def test_mysqld_qslower(self): + # Deliberately left empty -- mysqld_qslower requires an instance of + # MySQL to be running, or it fails to attach. + pass + + @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") + def test_offcputime(self): + self.run_with_duration("offcputime.py 1") + + @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") + def test_offwaketime(self): + self.run_with_duration("offwaketime.py 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_oomkill(self): + self.run_with_int("oomkill.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_opensnoop(self): + self.run_with_int("opensnoop.py") + + def test_pidpersec(self): + self.run_with_int("pidpersec.py") + + @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") + def test_profile(self): + self.run_with_duration("profile.py 1") + + def test_runqlat(self): + self.run_with_duration("runqlat.py 1 1") + + @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9") + def test_runqlen(self): + self.run_with_duration("runqlen.py 1 1") + + def test_slabratetop(self): + self.run_with_duration("slabratetop.py 1 1") + + def test_softirqs(self): + # TODO Temporary disabled as softirqs.py doesn't work on recent + # kernels (can't find some of its attach targets). Need to revisit + # it to use the softirq tracepoints. Tracked in bcc#1031. + # self.run_with_duration("softirqs.py 1 1") + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_solisten(self): + self.run_with_int("solisten.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_sslsniff(self): + self.run_with_int("sslsniff.py") + + @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") + def test_stackcount(self): + self.run_with_int("stackcount.py __kmalloc -i 1") + + @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") + def test_stacksnoop(self): + self.run_with_int("stacksnoop.py SyS_open") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_statsnoop(self): + self.run_with_int("statsnoop.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_syncsnoop(self): + self.run_with_int("syncsnoop.py") + + @skipUnless(kernel_version_ge(4,7), "requires kernel >= 4.7") + def test_syscount(self): + self.run_with_int("syscount.py -i 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_tcpaccept(self): + self.run_with_int("tcpaccept.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_tcpconnect(self): + self.run_with_int("tcpconnect.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_tcpconnlat(self): + self.run_with_int("tcpconnlat.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_tcplife(self): + self.run_with_int("tcpconnlat.py") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_tcpretrans(self): + self.run_with_int("tcpretrans.py") + + def test_tcptop(self): + self.run_with_duration("tcptop.py 1 1") + + def test_tplist(self): + self.run_with_duration("tplist.py -p %d" % os.getpid()) + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_trace(self): + self.run_with_int("trace.py SyS_open") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_ttysnoop(self): + self.run_with_int("ttysnoop.py /dev/console") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_ucalls(self): + # This attaches a large number (300+) kprobes, which can be slow, + # so use an increased timeout value. + self.run_with_int("ucalls.py -S %d" % os.getpid(), + timeout=30, kill_timeout=30) + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_uflow(self): + # The Python installed on the Ubuntu buildbot doesn't have USDT + # probes, so we can't run uflow. + # self.run_with_int("uflow.py python %d" % os.getpid()) + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_ugc(self): + # This requires a runtime that has GC probes to be installed. + # Python has them, but only in very recent versions. Skip. + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_uobjnew(self): + self.run_with_int("uobjnew.py c %d" % os.getpid()) + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_ustat(self): + self.run_with_duration("ustat.py 1 1") + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_uthreads(self): + self.run_with_int("uthreads.py %d" % os.getpid()) + + def test_vfscount(self): + self.run_with_int("vfscount.py") + + def test_vfsstat(self): + self.run_with_duration("vfsstat.py 1 1") + + def test_wakeuptime(self): + self.run_with_duration("wakeuptime.py 1") + + def test_xfsdist(self): + # Doesn't work on build bot because xfs functions not present in the + # kernel image. + # self.run_with_duration("xfsdist.py 1 1") + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_xfsslower(self): + # Doesn't work on build bot because xfs functions not present in the + # kernel image. + # self.run_with_int("xfsslower.py") + pass + + def test_zfsdist(self): + # Fails to attach the probe if zfs is not installed. + pass + + @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + def test_zfsslower(self): + # Fails to attach the probe if zfs is not installed. + pass + +if __name__ == "__main__": + main() diff --git a/tools/argdist.py b/tools/argdist.py index f1b78493be36..61f8e0060f9e 100755 --- a/tools/argdist.py +++ b/tools/argdist.py @@ -637,7 +637,7 @@ def _create_probes(self): self.probes.append(Probe(self, "hist", histspecifier)) if len(self.probes) == 0: print("at least one specifier is required") - exit() + exit(1) def _generate_program(self): bpf_source = """ @@ -695,10 +695,13 @@ def run(self): self._attach() self._main_loop() except: + exc_info = sys.exc_info() + sys_exit = exc_info[0] is SystemExit if self.args.verbose: traceback.print_exc() - elif sys.exc_info()[0] is not SystemExit: - print(sys.exc_info()[1]) + elif not sys_exit: + print(exc_info[1]) + exit(0 if sys_exit else 1) if __name__ == "__main__": Tool().run() diff --git a/tools/mdflush.py b/tools/mdflush.py index 955324a04698..b023b7606346 100755 --- a/tools/mdflush.py +++ b/tools/mdflush.py @@ -21,6 +21,7 @@ #include #include #include +#include struct data_t { u64 pid; diff --git a/tools/offwaketime.py b/tools/offwaketime.py old mode 100644 new mode 100755 diff --git a/tools/trace.py b/tools/trace.py index 1c6b33997d38..aac60e3f8a95 100755 --- a/tools/trace.py +++ b/tools/trace.py @@ -691,10 +691,13 @@ def run(self): self._attach_probes() self._main_loop() except: + exc_info = sys.exc_info() + sys_exit = exc_info[0] is SystemExit if self.args.verbose: traceback.print_exc() - elif sys.exc_info()[0] is not SystemExit: - print(sys.exc_info()[1]) + elif not sys_exit: + print(exc_info[1]) + exit(0 if sys_exit else 1) if __name__ == "__main__": Tool().run()