1#!/usr/bin/env python
2# Copyright (c) Sasha Goldshtein, 2017
3# Licensed under the Apache License, Version 2.0 (the "License")
4
5import distutils.version
6import subprocess
7import os
8import re
9from unittest import main, skipUnless, TestCase
10
11TOOLS_DIR = "../../tools/"
12
13def kernel_version_ge(major, minor):
14    # True if running kernel is >= X.Y
15    version = distutils.version.LooseVersion(os.uname()[2]).version
16    if version[0] > major:
17        return True
18    if version[0] < major:
19        return False
20    if minor and version[1] < minor:
21        return False
22    return True
23
24@skipUnless(kernel_version_ge(4,1), "requires kernel >= 4.1")
25class SmokeTests(TestCase):
26    # Use this for commands that have a built-in timeout, so they only need
27    # to be killed in case of a hard hang.
28    def run_with_duration(self, command, timeout=10):
29        full_command = TOOLS_DIR + command
30        self.assertEqual(0,     # clean exit
31                subprocess.call("timeout -s KILL %ds %s > /dev/null" %
32                                (timeout, full_command), shell=True))
33
34    # Use this for commands that don't have a built-in timeout, so we have
35    # to Ctrl-C out of them by sending SIGINT. If that still doesn't stop
36    # them, send a kill signal 5 seconds later.
37    def run_with_int(self, command, timeout=5, kill_timeout=5,
38                     allow_early=False, kill=False):
39        full_command = TOOLS_DIR + command
40        signal = "KILL" if kill else "INT"
41        rc = subprocess.call("timeout -s %s -k %ds %ds %s > /dev/null" %
42                (signal, kill_timeout, timeout, full_command), shell=True)
43        # timeout returns 124 if the program did not terminate prematurely,
44        # and returns 137 if we used KILL instead of INT. So there are three
45        # sensible scenarios:
46        #   1. The script is allowed to return early, and it did, with a
47        #      success return code.
48        #   2. The script timed out and was killed by the SIGINT signal.
49        #   3. The script timed out and was killed by the SIGKILL signal, and
50        #      this was what we asked for using kill=True.
51        self.assertTrue((rc == 0 and allow_early) or rc == 124
52                        or (rc == 137 and kill), "rc was %d" % rc)
53
54    def kmod_loaded(self, mod):
55        with open("/proc/modules", "r") as mods:
56            reg = re.compile("^%s\s" % mod)
57            for line in mods:
58                if reg.match(line):
59                    return 1
60                return 0
61
62    def setUp(self):
63        pass
64
65    def tearDown(self):
66        pass
67
68    def test_argdist(self):
69        self.run_with_duration("argdist.py -v -C 'p::do_sys_open()' -n 1 -i 1")
70
71    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
72    def test_bashreadline(self):
73        self.run_with_int("bashreadline.py")
74
75    def test_biolatency(self):
76        self.run_with_duration("biolatency.py 1 1")
77
78    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
79    def test_biosnoop(self):
80        self.run_with_int("biosnoop.py")
81
82    def test_biotop(self):
83        self.run_with_duration("biotop.py 1 1")
84
85    def test_bitesize(self):
86        self.run_with_int("biotop.py")
87
88    def test_bpflist(self):
89        self.run_with_duration("bpflist.py")
90
91    def test_btrfsdist(self):
92        # Will attempt to do anything meaningful only when btrfs is installed.
93        self.run_with_duration("btrfsdist.py 1 1")
94
95    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
96    def test_btrfsslower(self):
97        # Will attempt to do anything meaningful only when btrfs is installed.
98        self.run_with_int("btrfsslower.py", allow_early=True)
99
100    def test_cachestat(self):
101        self.run_with_duration("cachestat.py 1 1")
102
103    def test_cachetop(self):
104        # TODO cachetop doesn't like to run without a terminal, disabled
105        # for now.
106        # self.run_with_int("cachetop.py 1")
107        pass
108
109    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
110    def test_capable(self):
111        self.run_with_int("capable.py")
112
113    def test_cpudist(self):
114        self.run_with_duration("cpudist.py 1 1")
115
116    @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9")
117    def test_cpuunclaimed(self):
118        self.run_with_duration("cpuunclaimed.py 1 1")
119
120    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
121    def test_dbslower(self):
122        # Deliberately left empty -- dbslower requires an instance of either
123        # MySQL or PostgreSQL to be running, or it fails to attach.
124        pass
125
126    @skipUnless(kernel_version_ge(4,3), "requires kernel >= 4.3")
127    def test_dbstat(self):
128        # Deliberately left empty -- dbstat requires an instance of either
129        # MySQL or PostgreSQL to be running, or it fails to attach.
130        pass
131
132    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
133    def test_dcsnoop(self):
134        self.run_with_int("dcsnoop.py")
135
136    def test_dcstat(self):
137        self.run_with_duration("dcstat.py 1 1")
138
139    @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6")
140    def test_deadlock_detector(self):
141        # TODO This tool requires a massive BPF stack traces table allocation,
142        # which might fail the run or even trigger the oomkiller to kill some
143        # other processes. Disabling for now.
144        # self.run_with_int("deadlock_detector.py $(pgrep -n bash)", timeout=10)
145        pass
146
147    @skipUnless(kernel_version_ge(4,8), "requires kernel >= 4.8")
148    def test_execsnoop(self):
149        self.run_with_int("execsnoop.py")
150
151    def test_ext4dist(self):
152        self.run_with_duration("ext4dist.py 1 1")
153
154    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
155    def test_ext4slower(self):
156        self.run_with_int("ext4slower.py")
157
158    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
159    def test_filelife(self):
160        self.run_with_int("filelife.py")
161
162    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
163    def test_fileslower(self):
164        self.run_with_int("fileslower.py")
165
166    def test_filetop(self):
167        self.run_with_duration("filetop.py 1 1")
168
169    def test_funccount(self):
170        self.run_with_int("funccount.py __kmalloc -i 1")
171
172    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
173    def test_funclatency(self):
174        self.run_with_int("funclatency.py __kmalloc -i 1")
175
176    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
177    def test_funcslower(self):
178        self.run_with_int("funcslower.py __kmalloc")
179
180    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
181    def test_gethostlatency(self):
182        self.run_with_int("gethostlatency.py")
183
184    def test_hardirqs(self):
185        self.run_with_duration("hardirqs.py 1 1")
186
187    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
188    def test_killsnoop(self):
189        # Because killsnoop intercepts signals, if we send it a SIGINT we we
190        # we likely catch it while it is handling the data packet from the
191        # BPF program, and the exception from the SIGINT will be swallowed by
192        # ctypes. Therefore, we use SIGKILL.
193        # To reproduce the above issue, run killsnoop and in another shell run
194        # `kill -s SIGINT $(pidof python)`. As a result, killsnoop will print
195        # a traceback but will not exit.
196        self.run_with_int("killsnoop.py", kill=True)
197
198    @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9")
199    def test_llcstat(self):
200        # Requires PMU, which is not available in virtual machines.
201        pass
202
203    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
204    def test_mdflush(self):
205        self.run_with_int("mdflush.py")
206
207    @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6")
208    def test_memleak(self):
209        self.run_with_duration("memleak.py 1 1")
210
211    @skipUnless(kernel_version_ge(4,8), "requires kernel >= 4.8")
212    def test_mountsnoop(self):
213        self.run_with_int("mountsnoop.py")
214
215    @skipUnless(kernel_version_ge(4,3), "requires kernel >= 4.3")
216    def test_mysqld_qslower(self):
217        # Deliberately left empty -- mysqld_qslower requires an instance of
218        # MySQL to be running, or it fails to attach.
219        pass
220
221    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
222    def test_nfsslower(self):
223        if(self.kmod_loaded("nfs")):
224            self.run_with_int("nfsslower.py")
225        else:
226            pass
227
228    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
229    def test_nfsdist(self):
230        if(self.kmod_loaded("nfs")):
231            self.run_with_duration("nfsdist.py 1 1")
232        else:
233            pass
234
235    @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6")
236    def test_offcputime(self):
237        self.run_with_duration("offcputime.py 1")
238
239    @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6")
240    def test_offwaketime(self):
241        self.run_with_duration("offwaketime.py 1")
242
243    @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9")
244    def test_oomkill(self):
245        self.run_with_int("oomkill.py")
246
247    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
248    def test_opensnoop(self):
249        self.run_with_int("opensnoop.py")
250
251    def test_pidpersec(self):
252        self.run_with_int("pidpersec.py")
253
254    @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9")
255    def test_profile(self):
256        self.run_with_duration("profile.py 1")
257
258    def test_runqlat(self):
259        self.run_with_duration("runqlat.py 1 1")
260
261    @skipUnless(kernel_version_ge(4,9), "requires kernel >= 4.9")
262    def test_runqlen(self):
263        self.run_with_duration("runqlen.py 1 1")
264
265    @skipUnless(kernel_version_ge(4,8), "requires kernel >= 4.8")
266    def test_shmsnoop(self):
267        self.run_with_int("shmsnoop.py")
268
269    @skipUnless(kernel_version_ge(4,8), "requires kernel >= 4.8")
270    def test_sofdsnoop(self):
271        self.run_with_int("sofdsnoop.py")
272
273    def test_slabratetop(self):
274        self.run_with_duration("slabratetop.py 1 1")
275
276    @skipUnless(kernel_version_ge(4,7), "requires kernel >= 4.7")
277    def test_softirqs(self):
278        self.run_with_duration("softirqs.py 1 1")
279        pass
280
281    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
282    def test_solisten(self):
283        self.run_with_int("solisten.py")
284
285    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
286    def test_sslsniff(self):
287        self.run_with_int("sslsniff.py")
288
289    @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6")
290    def test_stackcount(self):
291        self.run_with_int("stackcount.py __kmalloc -i 1")
292
293    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
294    def test_statsnoop(self):
295        self.run_with_int("statsnoop.py")
296
297    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
298    def test_syncsnoop(self):
299        self.run_with_int("syncsnoop.py")
300
301    @skipUnless(kernel_version_ge(4,7), "requires kernel >= 4.7")
302    def test_syscount(self):
303        self.run_with_int("syscount.py -i 1")
304
305    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
306    def test_tcpaccept(self):
307        self.run_with_int("tcpaccept.py")
308
309    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
310    def test_tcpconnect(self):
311        self.run_with_int("tcpconnect.py")
312
313    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
314    def test_tcpconnlat(self):
315        self.run_with_int("tcpconnlat.py")
316
317    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
318    def test_tcplife(self):
319        self.run_with_int("tcplife.py")
320
321    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
322    def test_tcpretrans(self):
323        self.run_with_int("tcpretrans.py")
324
325    @skipUnless(kernel_version_ge(4, 7), "requires kernel >= 4.7")
326    def test_tcpdrop(self):
327        self.run_with_int("tcpdrop.py")
328
329    def test_tcptop(self):
330        self.run_with_duration("tcptop.py 1 1")
331
332    def test_tplist(self):
333        self.run_with_duration("tplist.py -p %d" % os.getpid())
334
335    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
336    def test_trace(self):
337        self.run_with_int("trace.py do_sys_open")
338
339    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
340    def test_ttysnoop(self):
341        self.run_with_int("ttysnoop.py /dev/console")
342
343    @skipUnless(kernel_version_ge(4,7), "requires kernel >= 4.7")
344    def test_ucalls(self):
345        self.run_with_int("lib/ucalls.py -l none -S %d" % os.getpid())
346
347    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
348    def test_uflow(self):
349        # The Python installed on the Ubuntu buildbot doesn't have USDT
350        # probes, so we can't run uflow.
351        # self.run_with_int("pythonflow.py %d" % os.getpid())
352        pass
353
354    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
355    def test_ugc(self):
356        # This requires a runtime that has GC probes to be installed.
357        # Python has them, but only in very recent versions. Skip.
358        pass
359
360    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
361    def test_uobjnew(self):
362        self.run_with_int("cobjnew.sh %d" % os.getpid())
363
364    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
365    def test_ustat(self):
366        self.run_with_duration("lib/ustat.py 1 1")
367
368    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
369    def test_uthreads(self):
370        self.run_with_int("lib/uthreads.py %d" % os.getpid())
371
372    def test_vfscount(self):
373        self.run_with_int("vfscount.py", timeout=15, kill_timeout=15)
374
375    def test_vfsstat(self):
376        self.run_with_duration("vfsstat.py 1 1")
377
378    @skipUnless(kernel_version_ge(4,6), "requires kernel >= 4.6")
379    def test_wakeuptime(self):
380        self.run_with_duration("wakeuptime.py 1")
381
382    def test_xfsdist(self):
383        # Doesn't work on build bot because xfs functions not present in the
384        # kernel image.
385        # self.run_with_duration("xfsdist.py 1 1")
386        pass
387
388    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
389    def test_xfsslower(self):
390        # Doesn't work on build bot because xfs functions not present in the
391        # kernel image.
392        # self.run_with_int("xfsslower.py")
393        pass
394
395    def test_zfsdist(self):
396        # Fails to attach the probe if zfs is not installed.
397        pass
398
399    @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4")
400    def test_zfsslower(self):
401        # Fails to attach the probe if zfs is not installed.
402        pass
403
404if __name__ == "__main__":
405    main()
406