#!/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()