| """Given a config file, runs tests""" |
| import os |
| import subprocess |
| import time |
| import shutil |
| from argparse import ArgumentParser |
| from testsuite_common import processLogLine |
|
|
| def parse_cmd(): |
| """Parse the command line arguments""" |
| description = "A python based speedtest suite for moses." |
| parser = ArgumentParser(description=description) |
| parser.add_argument("-c", "--configfile", action="store",\ |
| dest="configfile", required=True,\ |
| help="Specify test config file") |
| parser.add_argument("-s", "--singletest", action="store",\ |
| dest="singletestdir", default=None,\ |
| help="Single test name directory. Specify directory name,\ |
| not full path!") |
| parser.add_argument("-r", "--revision", action="store",\ |
| dest="revision", default=None,\ |
| help="Specify a specific revison for the test.") |
| parser.add_argument("-b", "--branch", action="store",\ |
| dest="branch", default=None,\ |
| help="Specify a branch for the test.") |
|
|
| arguments = parser.parse_args() |
| return arguments |
|
|
| def repoinit(testconfig, profiler=None): |
| """Determines revision and sets up the repo. If given the profiler optional |
| argument, wil init the profiler repo instead of the default one.""" |
| revision = '' |
| |
| if profiler == "gnu-profiler": |
| if testconfig.repo_prof is not None: |
| os.chdir(testconfig.repo_prof) |
| else: |
| raise ValueError('Profiling repo is not defined') |
| elif profiler == "google-profiler": |
| if testconfig.repo_gprof is not None: |
| os.chdir(testconfig.repo_gprof) |
| else: |
| raise ValueError('Profiling repo is not defined') |
| else: |
| os.chdir(testconfig.repo) |
| |
| if testconfig.branch != 'master': |
| subprocess.call(['git', 'checkout', testconfig.branch]) |
| rev, _ = subprocess.Popen(['git', 'rev-parse', 'HEAD'],\ |
| stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() |
| revision = str(rev).replace("\\n'", '').replace("b'", '') |
| else: |
| subprocess.call(['git checkout master'], shell=True) |
|
|
| |
| if testconfig.revision: |
| subprocess.call(['git', 'checkout', testconfig.revision]) |
| revision = testconfig.revision |
| elif testconfig.branch == 'master': |
| subprocess.call(['git pull'], shell=True) |
| rev, _ = subprocess.Popen(['git rev-parse HEAD'], stdout=subprocess.PIPE,\ |
| stderr=subprocess.PIPE, shell=True).communicate() |
| revision = str(rev).replace("\\n'", '').replace("b'", '') |
|
|
| return revision |
|
|
| class Configuration: |
| """A simple class to hold all of the configuration constatns""" |
| def __init__(self, repo, drop_caches, tests, testlogs, basebranch, baserev, repo_prof=None, repo_gprof=None): |
| self.repo = repo |
| self.repo_prof = repo_prof |
| self.repo_gprof = repo_gprof |
| self.drop_caches = drop_caches |
| self.tests = tests |
| self.testlogs = testlogs |
| self.basebranch = basebranch |
| self.baserev = baserev |
| self.singletest = None |
| self.revision = None |
| self.branch = 'master' |
|
|
| def additional_args(self, singletest, revision, branch): |
| """Additional configuration from command line arguments""" |
| self.singletest = singletest |
| if revision is not None: |
| self.revision = revision |
| if branch is not None: |
| self.branch = branch |
|
|
| def set_revision(self, revision): |
| """Sets the current revision that is being tested""" |
| self.revision = revision |
|
|
|
|
| class Test: |
| """A simple class to contain all information about tests""" |
| def __init__(self, name, command, ldopts, permutations, prof_command=None, gprof_command=None): |
| self.name = name |
| self.command = command |
| self.prof_command = prof_command |
| self.gprof_command = gprof_command |
| self.ldopts = ldopts.replace(' ', '').split(',') |
| self.permutations = permutations |
|
|
| def parse_configfile(conffile, testdir, moses_repo, moses_prof_repo=None, moses_gprof_repo=None): |
| """Parses the config file""" |
| command, ldopts, prof_command, gprof_command = '', '', None, None |
| permutations = [] |
| fileopen = open(conffile, 'r') |
| for line in fileopen: |
| line = line.split('#')[0] |
| if line == '' or line == '\n': |
| continue |
| opt, args = line.split(' ', 1) |
|
|
| if opt == 'Command:': |
| command = args.replace('\n', '') |
| if moses_prof_repo is not None: |
| prof_command = moses_prof_repo + '/bin/' + command |
| if moses_gprof_repo is not None: |
| gprof_command = moses_gprof_repo + '/bin/' + command |
| command = moses_repo + '/bin/' + command |
| elif opt == 'LDPRE:': |
| ldopts = args.replace('\n', '') |
| elif opt == 'Variants:': |
| permutations = args.replace('\n', '').replace(' ', '').split(',') |
| else: |
| raise ValueError('Unrecognized option ' + opt) |
| |
| testcase = Test(testdir, command, ldopts, permutations, prof_command, gprof_command) |
| fileopen.close() |
| return testcase |
|
|
| def parse_testconfig(conffile): |
| """Parses the config file for the whole testsuite.""" |
| repo_path, drop_caches, tests_dir, testlog_dir = '', '', '', '' |
| basebranch, baserev, repo_prof_path, repo_gprof_path = '', '', None, None |
| fileopen = open(conffile, 'r') |
| for line in fileopen: |
| line = line.split('#')[0] |
| if line == '' or line == '\n': |
| continue |
| opt, args = line.split(' ', 1) |
| if opt == 'MOSES_REPO_PATH:': |
| repo_path = args.replace('\n', '') |
| elif opt == 'DROP_CACHES_COMM:': |
| drop_caches = args.replace('\n', '') |
| elif opt == 'TEST_DIR:': |
| tests_dir = args.replace('\n', '') |
| elif opt == 'TEST_LOG_DIR:': |
| testlog_dir = args.replace('\n', '') |
| elif opt == 'BASEBRANCH:': |
| basebranch = args.replace('\n', '') |
| elif opt == 'BASEREV:': |
| baserev = args.replace('\n', '') |
| elif opt == 'MOSES_PROFILER_REPO:': |
| repo_prof_path = args.replace('\n', '') |
| elif opt == 'MOSES_GOOGLE_PROFILER_REPO:': |
| repo_gprof_path = args.replace('\n', '') |
| else: |
| raise ValueError('Unrecognized option ' + opt) |
| config = Configuration(repo_path, drop_caches, tests_dir, testlog_dir,\ |
| basebranch, baserev, repo_prof_path, repo_gprof_path) |
| fileopen.close() |
| return config |
|
|
| def get_config(): |
| """Builds the config object with all necessary attributes""" |
| args = parse_cmd() |
| config = parse_testconfig(args.configfile) |
| config.additional_args(args.singletestdir, args.revision, args.branch) |
| revision = repoinit(config) |
| if config.repo_prof is not None: |
| repoinit(config, "gnu-profiler") |
| if config.repo_gprof is not None: |
| repoinit(config, "google-profiler") |
| config.set_revision(revision) |
| return config |
|
|
| def check_for_basever(testlogfile, basebranch): |
| """Checks if the base revision is present in the testlogs""" |
| filetoopen = open(testlogfile, 'r') |
| for line in filetoopen: |
| templine = processLogLine(line) |
| if templine.branch == basebranch: |
| return True |
| return False |
|
|
| def split_time(filename): |
| """Splits the output of the time function into seperate parts. |
| We will write time to file, because many programs output to |
| stderr which makes it difficult to get only the exact results we need.""" |
| timefile = open(filename, 'r') |
| realtime = float(timefile.readline().replace('\n', '').split()[1]) |
| usertime = float(timefile.readline().replace('\n', '').split()[1]) |
| systime = float(timefile.readline().replace('\n', '').split()[1]) |
| timefile.close() |
|
|
| return (realtime, usertime, systime) |
|
|
|
|
| def write_log(time_file, logname, config): |
| """Writes to a logfile""" |
| log_write = open(config.testlogs + '/' + logname, 'a') |
| date_run = time.strftime("%d.%m.%Y %H:%M:%S") |
| realtime, usertime, systime = split_time(time_file) |
|
|
| |
| writestr = date_run + " " + config.revision + " Testname: " + logname +\ |
| " RealTime: " + str(realtime) + " UserTime: " + str(usertime) +\ |
| " SystemTime: " + str(systime) + " Branch: " + config.branch +'\n' |
| log_write.write(writestr) |
| log_write.close() |
|
|
| def write_gprof(command, name, variant, config): |
| """Produces a gprof report from a gmon file""" |
| |
| output_dir = config.testlogs + '/' + name |
| if not os.path.exists(output_dir): |
| os.makedirs(output_dir) |
| outputfile = output_dir + '/' + time.strftime("%d.%m.%Y_%H:%M:%S") + '_' + name + '_' + variant |
|
|
| |
| gmon_path = os.getcwd() + '/gmon.out' |
| executable_path = command.split(' ')[0] |
| gprof_command = 'gprof ' + executable_path + ' ' + gmon_path + ' > ' + outputfile |
| subprocess.call([gprof_command], shell=True) |
| os.remove(gmon_path) |
|
|
| def write_pprof(name, variant, config): |
| """Copies the google-perftools profiler output to the corresponding test directory""" |
| output_dir = config.testlogs + '/' + name |
| if not os.path.exists(output_dir): |
| os.makedirs(output_dir) |
| outputfile = output_dir + '/pprof_' + time.strftime("%d.%m.%Y_%H:%M:%S") + '_' + name + '_' + variant |
| shutil.move("/tmp/moses.prof", outputfile) |
|
|
|
|
| def execute_test(command, path, name, variant, config, profile=None): |
| """Executes a testcase given a whole command, path to the test file output, |
| name of the test and variant tested. Config is the global configuration""" |
| subprocess.Popen([command], stdout=None, stderr=subprocess.PIPE, shell=True).communicate() |
| if profile is None: |
| write_log(path, name + '_' + variant, config) |
| elif profile == "gnu-profiler": |
| write_gprof(command, name, variant, config) |
| elif profile == "google-profiler": |
| write_pprof(name, variant, config) |
|
|
|
|
| def execute_tests(testcase, cur_directory, config): |
| """Executes timed tests based on the config file""" |
| |
| time_command = ' time -p -o /tmp/time_moses_tests ' |
| time_path = '/tmp/time_moses_tests' |
|
|
| |
| |
| os.chdir(config.tests + '/' + cur_directory) |
| |
| subprocess.call(['sync'], shell=True) |
| subprocess.call([config.drop_caches], shell=True) |
| |
| print(testcase.name) |
| if 'vanilla' in testcase.permutations: |
| |
| whole_command = time_command + testcase.command |
|
|
| |
| execute_test(whole_command, time_path, testcase.name, 'vanilla', config) |
| if 'cached' in testcase.permutations: |
| execute_test(whole_command, time_path, testcase.name, 'vanilla_cached', config) |
|
|
| |
| if 'ldpre' in testcase.permutations: |
| for opt in testcase.ldopts: |
| |
| subprocess.call(['sync'], shell=True) |
| subprocess.call([config.drop_caches], shell=True) |
|
|
| |
| whole_command = 'LD_PRELOAD=' + opt + time_command + testcase.command |
| variant = 'ldpre_' + opt |
|
|
| |
| execute_test(whole_command, time_path, testcase.name, variant, config) |
| if 'cached' in testcase.permutations: |
| execute_test(whole_command, time_path, testcase.name, variant + '_cached', config) |
|
|
| |
| |
| if 'profile' in testcase.permutations: |
| subprocess.call(['sync'], shell=True) |
| subprocess.call([config.drop_caches], shell=True) |
|
|
| if 'vanilla' in testcase.permutations: |
| whole_command = testcase.prof_command |
| execute_test(whole_command, time_path, testcase.name, 'profile', config, "gnu-profiler") |
| if 'cached' in testcase.permutations: |
| execute_test(whole_command, time_path, testcase.name, 'profile_cached', config, "gnu-profiler") |
|
|
| if 'ldpre' in testcase.permutations: |
| for opt in testcase.ldopts: |
| |
| subprocess.call(['sync'], shell=True) |
| subprocess.call([config.drop_caches], shell=True) |
|
|
| |
| whole_command = 'LD_PRELOAD=' + opt + " " + testcase.prof_command |
| variant = 'profile_ldpre_' + opt |
|
|
| |
| execute_test(whole_command, time_path, testcase.name, variant, config, "gnu-profiler") |
| if 'cached' in testcase.permutations: |
| execute_test(whole_command, time_path, testcase.name, variant + '_cached', config, "gnu-profiler") |
|
|
| |
| if 'google-profiler' in testcase.permutations: |
| subprocess.call(['sync'], shell=True) |
| subprocess.call([config.drop_caches], shell=True) |
|
|
| |
| whole_command = "CPUPROFILE=/tmp/moses.prof " + testcase.gprof_command |
|
|
| |
| execute_test(whole_command, time_path, testcase.name, 'vanilla', config, 'google-profiler') |
| if 'cached' in testcase.permutations: |
| execute_test(whole_command, time_path, testcase.name, 'vanilla_cached', config, 'google-profiler') |
|
|
| |
| if 'ldpre' in testcase.permutations: |
| for opt in testcase.ldopts: |
| |
| subprocess.call(['sync'], shell=True) |
| subprocess.call([config.drop_caches], shell=True) |
|
|
| |
| whole_command = 'LD_PRELOAD=' + opt + " " + whole_command |
| variant = 'ldpre_' + opt |
|
|
| |
| execute_test(whole_command, time_path, testcase.name, variant, config, 'google-profiler') |
| if 'cached' in testcase.permutations: |
| execute_test(whole_command, time_path, testcase.name, variant + '_cached', config, 'google-profiler') |
|
|
|
|
| |
| if __name__ == '__main__': |
| CONFIG = get_config() |
| ALL_DIR = os.listdir(CONFIG.tests) |
|
|
| |
| |
| |
| FIRSTTIME = [] |
| TESTLOGS = [] |
| |
| for listline in os.listdir(CONFIG.testlogs): |
| listline = listline.replace('_vanilla', '') |
| listline = listline.replace('_cached', '') |
| listline = listline.replace('_ldpre', '') |
| TESTLOGS.append(listline) |
| for directory in ALL_DIR: |
| if directory not in TESTLOGS: |
| FIRSTTIME.append(directory) |
|
|
| |
| |
| |
| |
| |
|
|
| for logfile in os.listdir(CONFIG.testlogs): |
| logfile_name = CONFIG.testlogs + '/' + logfile |
| if os.path.isfile(logfile_name) and not check_for_basever(logfile_name, CONFIG.basebranch): |
| logfile = logfile.replace('_vanilla', '') |
| logfile = logfile.replace('_cached', '') |
| logfile = logfile.replace('_ldpre', '') |
| FIRSTTIME.append(logfile) |
| FIRSTTIME = list(set(FIRSTTIME)) |
|
|
| if FIRSTTIME != []: |
| |
| BASECONFIG = Configuration(CONFIG.repo, CONFIG.drop_caches,\ |
| CONFIG.tests, CONFIG.testlogs, CONFIG.basebranch,\ |
| CONFIG.baserev, CONFIG.repo_prof, CONFIG.repo_gprof) |
| BASECONFIG.additional_args(None, CONFIG.baserev, CONFIG.basebranch) |
| |
| REVISION = repoinit(BASECONFIG) |
| BASECONFIG.set_revision(REVISION) |
| |
| os.chdir(BASECONFIG.repo) |
| subprocess.call(['./previous.sh'], shell=True) |
| |
| if BASECONFIG.repo_prof is not None: |
| repoinit(BASECONFIG, "gnu-profiler") |
| os.chdir(BASECONFIG.repo_prof) |
| subprocess.call(['./previous.sh'], shell=True) |
|
|
| if BASECONFIG.repo_gprof is not None: |
| repoinit(BASECONFIG, "google-profiler") |
| os.chdir(BASECONFIG.repo_gprof) |
| subprocess.call(['./previous.sh'], shell=True) |
|
|
| |
| for directory in FIRSTTIME: |
| cur_testcase = parse_configfile(BASECONFIG.tests + '/' + directory +\ |
| '/config', directory, BASECONFIG.repo, BASECONFIG.repo_prof, BASECONFIG.repo_gprof) |
| execute_tests(cur_testcase, directory, BASECONFIG) |
|
|
| |
| repoinit(CONFIG) |
| if BASECONFIG.repo_prof is not None: |
| repoinit(CONFIG, "gnu-profiler") |
|
|
| if BASECONFIG.repo_gprof is not None: |
| repoinit(CONFIG, "google-profiler") |
|
|
| |
| os.chdir(CONFIG.repo) |
| subprocess.call(['./previous.sh'], shell=True) |
| if CONFIG.repo_prof is not None: |
| os.chdir(CONFIG.repo_prof) |
| subprocess.call(['./previous.sh'], shell=True) |
|
|
| if CONFIG.repo_gprof is not None: |
| os.chdir(CONFIG.repo_gprof) |
| subprocess.call(['./previous.sh'], shell=True) |
|
|
| if CONFIG.singletest: |
| TESTCASE = parse_configfile(CONFIG.tests + '/' +\ |
| CONFIG.singletest + '/config', CONFIG.singletest, CONFIG.repo, CONFIG.repo_prof, CONFIG.repo_gprof) |
| execute_tests(TESTCASE, CONFIG.singletest, CONFIG) |
| else: |
| for directory in ALL_DIR: |
| cur_testcase = parse_configfile(CONFIG.tests + '/' + directory +\ |
| '/config', directory, CONFIG.repo, CONFIG.repo_prof, CONFIG.repo_gprof) |
| execute_tests(cur_testcase, directory, CONFIG) |
|
|