author | Alberto Bertogli
<albertito@blitiri.com.ar> 2009-03-28 03:14:20 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2009-03-28 03:16:52 UTC |
parent | 25424aaa368622580a206ddd8f3ba2e5ecddf75d |
tests/stress/jiostress | +203 | -0 |
diff --git a/tests/stress/jiostress b/tests/stress/jiostress new file mode 100755 index 0000000..ca4a83a --- /dev/null +++ b/tests/stress/jiostress @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 + +""" +This application is a stress tester for libjio. It's not a traditional stress +test like fsx (which can be used to test libjio using the preloading library), +but uses fault injection to check how the library behaves under random +failures. +""" + +import sys +import os +import random +import traceback +import libjio + +try: + import fiu +except ImportError: + print() + print("Error: unable to load fiu module. This test needs libfiu") + print("support. Please install libfiu and recompile libjio with FI=1.") + print() + raise + +# +# Auxiliary stuff +# + +randsrc = open('/dev/urandom', mode = 'rb') +def randbytes(n): + "Returns n random bytes" + if n < 64: + return randsrc.read(n) + + # to avoid reading too much from urandom (which needlessly stresses + # the kernel), just get a small random stream and repeat it + s = randsrc.read(64) + while len(s) < n: + s += s + s[::-1] + + return s[:n] + +def randfrange(maxend, maxsize): + start = random.randint(0, maxend - 1) + size = random.randint(0, (maxend - 1) - start) % maxsize + return start, start + size + +class ConsistencyError (Exception): + pass + + +# +# The test itself +# + +class Stresser: + def __init__(self, fname, fsize, nops): + self.fname = fname + self.fsize = fsize + self.nops = nops + + self.maxoplen = min(int(self.fsize / 4), + 5 * 1024 * 1024) + + self.jf = libjio.open(fname, + libjio.O_RDWR | libjio.O_CREAT, 0o600) + self.f = open(fname, mode = 'rb') + + # data used for consistency checks + self.current_range = (0, 0) + self.prev_data = b"" + self.new_data = b"" + + def pread(self, start, end): + ppos = self.f.tell() + self.f.seek(start, 0) + r = self.f.read(end - start) + self.f.seek(ppos, 0) + return r + + def randwrite(self): + pid = os.fork() + if pid == 0: + # child + try: + start, end = randfrange(self.fsize, + self.maxoplen) + + # read an extended range so we can check we + # only wrote what we were supposed to + estart = max(0, start - 32) + eend = min(self.fsize, end + 32) + self.current_range = (estart, eend) + self.prev_data = self.pread(estart, eend) + + nd = randbytes(end - start) + self.new_data = self.prev_data[estart:start] \ + + nd + self.prev_data[end:eend] + self.jf.pwrite(nd, start) + except IOError: + sys.exit(1) + except: + traceback.print_exc() + sys.exit(1) + sys.exit(0) + else: + # parent + id, status = os.waitpid(pid, 0) + if not os.WIFEXITED(status): + raise RuntimeError(status) + + if os.WEXITSTATUS(status) != 0: + return False + return True + + def verify(self): + # NOTE: must not use self.jf + real_data = self.pread(self.current_range[0], + self.current_range[1]) + if real_data not in (self.prev_data, self.new_data): + raise ConsistencyError + + def reopen(self): + self.jf = None + r = libjio.jfsck(self.fname) + self.verify() + libjio.jfsck_cleanup(self.fname) + + self.jf = libjio.open(self.fname, + libjio.O_RDWR | libjio.O_CREAT, 0o600) + return r + + def fiu_enable(self): + fiu.enable_random('jio/*', probability = 0.02) + + def fiu_disable(self): + fiu.disable('jio/*') + + def run(self): + self.fiu_enable() + nfailures = 0 + sys.stdout.write(" ") + for i in range(1, self.nops + 1): + sys.stdout.write(".") + if i % 10 == 0: + sys.stdout.write(" ") + if i % 50 == 0: + sys.stdout.write(" %d\n" % i) + sys.stdout.write(" ") + sys.stdout.flush() + if not self.randwrite(): + nfailures += 1 + self.fiu_disable() + r = self.reopen() + assert r['total'] <= 1 + self.fiu_enable() + self.verify() + sys.stdout.write("\n") + sys.stdout.flush() + self.fiu_disable() + return nfailures + + +# +# Main +# + +def usage(): + print(""" +Use: jiostress <file name> <file size in Mb> [<number of operations>] + +If the number of operations is not provided, the default (1000) will be +used.""") + + +def main(): + try: + fname = sys.argv[1] + fsize = int(sys.argv[2]) * 1024 * 1024 + nops = 1000 + if len(sys.argv) >= 4: + nops = int(sys.argv[3]) + except: + usage() + sys.exit(1) + + s = Stresser(fname, fsize, nops) + print("Running stress test") + nfailures = s.run() + print("Stress test completed") + print(" %d operations" % nops) + print(" %d simulated failures" % nfailures) + + r = libjio.jfsck(fname) + assert r['total'] == 0 + libjio.jfsck_cleanup(fname) + print("Final check completed") + #os.unlink(fname) + + +if __name__ == '__main__': + main() +