#!/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 import re 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 kmod_loaded(self, mod): with open("/proc/modules", "r") as mods: reg = re.compile("^%s\s" % mod) for line in mods: if reg.match(line): return 1 return 0 def setUp(self): pass def tearDown(self): pass def test_argdist(self): self.run_with_duration("argdist.py -v -C 'p::do_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_bpflist(self): self.run_with_duration("bpflist.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,8), "requires kernel >= 4.8") 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_funcslower(self): self.run_with_int("funcslower.py __kmalloc") @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,4), "requires kernel >= 4.4") def test_nfsslower(self): if(self.kmod_loaded("nfs")): self.run_with_int("nfsslower.py") else: pass @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") def test_nfsdist(self): if(self.kmod_loaded("nfs")): self.run_with_duration("nfsdist.py 1 1") else: 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,9), "requires kernel >= 4.9") 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") @skipUnless(kernel_version_ge(4,7), "requires kernel >= 4.7") def test_softirqs(self): 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,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("tcplife.py") @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") def test_tcpretrans(self): self.run_with_int("tcpretrans.py") @skipUnless(kernel_version_ge(4, 7), "requires kernel >= 4.7") def test_tcpdrop(self): self.run_with_int("tcpdrop.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 do_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("lib/ucalls.py -l none -S %d" % os.getpid(), timeout=60, kill_timeout=60) @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("pythonflow.py %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("cobjnew.sh %d" % os.getpid()) @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") def test_ustat(self): self.run_with_duration("lib/ustat.py 1 1") @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") def test_uthreads(self): self.run_with_int("lib/uthreads.py %d" % os.getpid()) def test_vfscount(self): self.run_with_int("vfscount.py", timeout=15, kill_timeout=15) def test_vfsstat(self): self.run_with_duration("vfsstat.py 1 1") @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6") 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()