#!/usr/bin/env python # # This is a test program meant to heavily exercise log and metadata # writeback. If you combine this with power failures, it should result # in filesystem corruptions a significant percent of the time. # # The workload has 4 parts: # # 1) A directory tree full of empty files with very long names (240 chars) # 2) A process hogging a significant percent of system ram. This must # be enough to force constant metadata writeback due to memory pressure # 3) A process constantly writing, fsyncing and truncating to zero a single 64k # file # 4) A process constantly renaming the files with very long names from (1) # between long-named-file.0 and long-named-file.1 # # In order to consistently cause corruptions, the size of the directory from # (1) needs to be at least as large as the ext3 log. This is controlled with # the -s command line option # # The amount of memory pinned down is controlled with -p # # Usage: # The program first creates the files in a directory called barrier-test # then it starts procs to pin ram and run the constant fsyncs. After # each phase has run long enough, they print out a statement about # being ready, along with some other debugging output: # # Memory pin ready # fsyncs ready # Renames ready # # Once you see all three ready lines, turn off power. Don't use the # power button on the front of the machine, either pull the plug, use # the power switch on the back of the machine, or use an external controller # # Written by Chris Mason import sys, os, sha, random, mmap from optparse import OptionParser total_name_bytes = 0 counter = 0 errors = 0 sha = sha.new() salt = file("/dev/urandom").read(256) sha.update(salt) def read_files(basedir, base_names): global total_name_bytes global counter global errors for x in os.listdir(basedir): total_name_bytes += len(x) counter += 1 full = os.path.join(basedir, x) if not full.endswith(".0") and not full.endswith(".1"): continue num = int(x[-1]) short = x[0:-2] if short in base_names: sys.stderr.write("warning: conflict on %s first %d dup %d\n" % (short[0:10], base_names[short], num)) errors += 1 else: base_names[short] = num def create_files(sha, basedir, base_names, size): global total_name_bytes global counter while total_name_bytes < size: s = str(counter) counter += 1 sha.update(s) s = sha.hexdigest() * 4 total_name_bytes += len(s) base_names[s] = 0 fp = file(os.path.join(basedir, s + ".0"), 'w') fp.close() if counter % 10000 == 0: print "%d files %d MB total" % (counter, total_name_bytes / (1024 * 1024)) def run_fsyncs(basedir): pid = os.fork() if pid: os.waitpid(pid, os.WNOHANG) return fp = file(os.path.join(basedir, "bigfile"), 'w') buf = "a" * (64 * 1024) operations = 0 while True: fp.write(buf) os.fsync(fp.fileno()) fp.truncate(0) fp.seek(0) operations += 1 if operations % 100 == 0: sys.stderr.write("f:%d " % operations) if operations == 300: sys.stderr.write("\nfsyncs ready\n") def run_hog(hogmb): pid = os.fork() if pid: os.waitpid(pid, os.WNOHANG) return fp = file("/dev/zero", 'w+') hogmb *= 1024 * 1024 mm = mmap.mmap(fp.fileno(), hogmb, mmap.MAP_PRIVATE, mmap.PROT_READ | mmap.PROT_WRITE) operations = 0 pos = 0 didprint = 0 buf = 'b' * 1024 * 1024 while True: mm.write(buf) if mm.tell() >= hogmb: mm.seek(0) pos = 0 operations += 1 if not didprint: sys.stderr.write("\nMemory pin ready\n") didprint = 1 def run_renames(basedir, base_names): keys = base_names.keys() operations = 0 while True: name = random.choice(keys) num = base_names[name] next = (num + 1) % 2 os.rename(os.path.join(basedir, name + "." + str(num)), os.path.join(basedir, name + "." + str(next))) base_names[name] = next operations += 1 if operations % 1000 == 0: sys.stderr.write("r:%d " % operations) if operations == 10000: sys.stderr.write("\nRenames ready\n") base_names = {} usage = "usage: %prog [options]" parser = OptionParser(usage=usage) parser.add_option("-i", "--init", help="Init directory", default=False, action="store_true") parser.add_option("-d", "--dir", help="working dir", default="barrier-test") parser.add_option("-s", "--size", help="Working set in MB", type="int", default=32) parser.add_option("-c", "--check", help="Check directory only", default=False, action="store_true") parser.add_option("-p", "--pin-mb", help="Amount of ram (MB) to pin", default=512, type="int") (options, args) = parser.parse_args() sys.stderr.write("Run init, don't cut the power yet\n") if not os.path.exists(options.dir): os.makedirs(options.dir, 0700) options.init = True else: read_files(options.dir, base_names) print "found %d files %d MB in %s" % (counter, total_name_bytes / (1024 * 1024), options.dir) options.size *= 1024 * 1024 if options.check: print "Check complete found %d errors" % errors if errors: sys.exit(1) else: sys.exit(0) if total_name_bytes < options.size: create_files(sha, options.dir, base_names, options.size) sys.stderr.write("Starting metadata operations now\n") run_fsyncs(options.dir) run_hog(options.pin_mb) run_renames(options.dir, base_names)