#! /usr/bin/env python # execute(), fetch(), notify() are based on https://github.com/getlantern/build-automation/blob/master/build.py import argparse import os import string import time import traceback from subprocess import Popen, PIPE repo_url = 'https://github.com/Cyan4973/zstd.git' test_dir_name = 'speedTest' def log(text): print time.strftime("%Y/%m/%d %H:%M:%S") + ' - ' + text def execute(command, print_output=True): log("> " + command) popen = Popen(command, stdout=PIPE, stderr=PIPE, shell=True, cwd=execute.cwd) itout = iter(popen.stdout.readline, b"") iterr = iter(popen.stderr.readline, b"") stdout_lines = list(itout) if print_output: print ''.join(stdout_lines) stderr_lines = list(iterr) if stderr_lines: print ''.join(stderr_lines) popen.communicate() if popen.returncode is not None and popen.returncode != 0: raise RuntimeError(''.join(stderr_lines)) return stdout_lines + stderr_lines execute.cwd = None def fetch(): execute('git fetch -p') output = execute('git branch -rl') for line in output: if "HEAD" in line: output.remove(line) # remove "origin/HEAD -> origin/dev" branches = map(lambda l: l.strip(), output) return map(lambda b: (b, execute('git show -s --format=%h ' + b)[0].strip()), branches) def notify(branch, commit, last_commit): text_tmpl = string.Template('Changes since $last_commit:\r\n$commits') branch = branch.split('/')[1] fmt = '--format="%h: (%an) %s, %ar"' if last_commit is None: commits = execute('git log -n 10 %s %s' % (fmt, commit)) else: commits = execute('git --no-pager log %s %s..%s' % (fmt, last_commit, commit)) text = text_tmpl.substitute({'last_commit': last_commit, 'commits': ''.join(commits)}) print str("commits for %s: %s" % (commit, text)) def compile(branch, commit, dry_run): local_branch = string.split(branch, '/')[1] version = local_branch.rpartition('-')[2] version = version + '_' + commit execute('git checkout -- . && git checkout ' + branch) if not dry_run: execute('VERSION=' + version + '; make clean zstdprogram') def get_last_commit(resultsFileName): if not os.path.isfile(resultsFileName): return None, None, None commit = None cspeed = [] dspeed = [] with open(resultsFileName,'r') as f: for line in f: words = line.split() if len(words) == 2: # branch + commit commit = words[1]; cspeed = [] dspeed = [] if (len(words) == 8): cspeed.append(float(words[3])) dspeed.append(float(words[5])) #if commit != None: # print "commit=%s cspeed=%s dspeed=%s" % (commit, cspeed, dspeed) return commit, cspeed, dspeed def benchmark_and_compare(branch, commit, resultsFileName, lastCLevel, testFilePath, fileName, last_cspeed, last_dspeed, lower_limit, maxLoadAvg): while os.getloadavg()[0] > maxLoadAvg: print "bench loadavg=%.2f is higher than %s" % (os.getloadavg()[0], maxLoadAvg) time.sleep(30) start_load = str(os.getloadavg()) result = execute('programs/zstd -qb1e' + str(lastCLevel) + ' ' + testFilePath) end_load = str(os.getloadavg()) linesExpected = lastCLevel + 2; if len(result) != linesExpected: print "len(result)=%d is different that expected %d" % (len(result), linesExpected) return "" with open(resultsFileName, "a") as myfile: myfile.write(branch + " " + commit + "\n") myfile.writelines(result) myfile.close() if (last_cspeed == None): return "" commit, cspeed, dspeed = get_last_commit(resultsFileName) text = "" for i in range(0, min(len(cspeed), len(last_cspeed))): if (cspeed[i]/last_cspeed[i] < lower_limit): text += "WARNING: File=%s level=%d cspeed=%s last=%s diff=%s\n" % (fileName, i+1, cspeed[i], last_cspeed[i], cspeed[i]/last_cspeed[i]) if (dspeed[i]/last_dspeed[i] < lower_limit): text += "WARNING: File=%s level=%d dspeed=%s last=%s diff=%s\n" % (fileName, i+1, dspeed[i], last_dspeed[i], dspeed[i]/last_dspeed[i]) if text: text += "maxLoadAvg=%s load average at start=%s end=%s\n" % (maxLoadAvg, start_load, end_load) return text def send_email(branch, commit, last_commit, emails, text, results_files, logFileName, lower_limit): with open(logFileName, "w") as myfile: myfile.writelines(text) myfile.close() execute("mutt -s \"[ZSTD_speedTest] Warning for branch=" + branch + " commit=" + commit + " last_commit=" + last_commit + " speed<" + str(lower_limit) + "\" " + emails + " -a " + results_files + " < " + logFileName) def main(args, test_path, clone_path, testFilePaths): print "test_path=%s" % test_path print "clone_path=%s" % clone_path print "testFilePath(%s)=%s" % (len(testFilePaths), testFilePaths) print "emails=%s" % args.emails print "maxLoadAvg=%s" % args.maxLoadAvg print "lowerLimit=%s" % args.lowerLimit print "lastCLevel=%s" % args.lastCLevel print "sleepTime=%s" % args.sleepTime print "dry_run=%s" % args.dry_run for branch, commit in fetch(): log("checking branch %s: head %s" % (branch, commit)) try: commitFileName = test_path + "/commit_" + branch.replace("/", "_") if os.path.isfile(commitFileName): last_commit = file(commitFileName, 'r').read() else: last_commit = None file(commitFileName, 'w').write(commit) if commit == last_commit: log("skipping branch %s: head %s already processed" % (branch, commit)) else: log("build branch %s: head %s is different from prev %s" % (branch, commit, last_commit)) compile(branch, commit, args.dry_run) logFileName = test_path + "/log_" + branch.replace("/", "_") text_to_send = [] results_files = "" for filePath in testFilePaths: fileName = filePath.rpartition('/')[2] resultsFileName = test_path + "/results_" + branch.replace("/", "_") + "_" + fileName last_commit, cspeed, dspeed = get_last_commit(resultsFileName) if not args.dry_run: text = benchmark_and_compare(branch, commit, resultsFileName, args.lastCLevel, filePath, fileName, cspeed, dspeed, args.lowerLimit, args.maxLoadAvg) if text: text = benchmark_and_compare(branch, commit, resultsFileName, args.lastCLevel, filePath, fileName, cspeed, dspeed, args.lowerLimit, args.maxLoadAvg) if text: text_to_send.append(text) results_files += resultsFileName + " " if text_to_send: send_email(branch, commit, last_commit, args.emails, text_to_send, results_files, logFileName, args.lowerLimit) notify(branch, commit, last_commit) except Exception as e: stack = traceback.format_exc() log("Error build %s, error %s" % (branch, str(e)) ) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('testFileNames', help='file names list for speed test') parser.add_argument('emails', help='e-mails to send warnings') parser.add_argument('--lowerLimit', type=float, help='send email if speed is lower than given limit e.g. 0.98', default=0.98) parser.add_argument('--maxLoadAvg', type=float, help='maximum load average to start testing', default=0.75) parser.add_argument('--lastCLevel', type=int, help='last compression level for testing', default=5) parser.add_argument('--sleepTime', type=int, help='frequency of checking in seconds', default=60) parser.add_argument('--dry-run', dest='dry_run', action='store_true', help='not build', default=False) args = parser.parse_args() # check if test files are accessible testFileNames = args.testFileNames.split() testFilePaths = [] for fileName in testFileNames: if os.path.isfile(fileName): testFilePaths.append(os.path.abspath(fileName)) else: raise RuntimeError("File not found: " + fileName) test_path = os.getcwd() + '/' + test_dir_name # /path/to/zstd/tests/speedTest clone_path = test_path + '/' + 'zstd' # /path/to/zstd/tests/speedTest/zstd # clone ZSTD repo if needed if not os.path.isdir(test_path): os.mkdir(test_path) if not os.path.isdir(clone_path): execute.cwd = test_path execute('git clone ' + repo_url) if not os.path.isdir(clone_path): raise RuntimeError("ZSTD clone not found: " + clone_path) execute.cwd = clone_path while True: pid = str(os.getpid()) pidfile = "./speedTest.pid" if os.path.isfile(pidfile): print "%s already exists, exiting" % pidfile else: file(pidfile, 'w').write(pid) try: loadavg = os.getloadavg()[0] if (loadavg <= args.maxLoadAvg): main(args, test_path, clone_path, testFilePaths) else: print "loadavg=%.2f is higher than %s" % (loadavg, args.maxLoadAvg) finally: os.unlink(pidfile) time.sleep(args.sleepTime)