#!/usr/bin/env python """ Utility script for submitting benchmark results to an LNT server. """ import datetime import json import optparse import subprocess import sys import urllib import urllib2 # Test status codes. PASS = 0 FAIL = 1 XFAIL = 2 ### def capture_with_result(args, include_stderr=False): """capture_with_result(command) -> (output, exit code) Run the given command (or argv list) in a shell and return the standard output and exit code.""" stderr = subprocess.PIPE if include_stderr: stderr = subprocess.STDOUT try: p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=stderr) except OSError,e: if e.errno == errno.ENOENT: fatal('no such file or directory: %r when running %s.' % ( args[0], ' '.join(args))) raise out,_ = p.communicate() return out,p.wait() def capture(args, include_stderr=False): """capture(command) - Run the given command (or argv list) in a shell and return the standard output.""" return capture_with_result(args, include_stderr)[0] def timestamp(): return datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') ### def submit_results_to_server(results_data, submit_url): # Submit the URL encoded data. data = urllib.urlencode({ 'input_data' : results_data, 'commit' : '1' }) response = urllib2.urlopen(urllib2.Request(submit_url, data)) result_data = response.read() # The result is expected to be a JSON object. try: return json.loads(result_data) except: import traceback print "Unable to load result, not a valid JSON object." print print "Traceback:" traceback.print_exc() print print "Result:" print result_data return ### def main(): parser = optparse.OptionParser("""%prog [options] """) parser.add_option("", "--submit", dest="submit_url", metavar="URL", help="Submit results to the given URL", action="store", type=str, default=None) parser.add_option("", "--output", dest="output", metavar="PATH", help="Write raw report data to PATH", action="store", type=str, default=None) parser.add_option("", "--machine-name", dest="machine_name", metavar="NAME", help="Set the machine name to embed in the report", action="store", type=str, default=None) parser.add_option("", "--run-order", dest="run_order", metavar="REVISION", help="Set the run order to embed in the report", action="store", type=int, default=None) opts, args = parser.parse_args() # At least one of --submit or --output is required. if len(args) != 1: parser.error("incorrect number of arguments") if opts.submit_url is None and opts.output is None: parser.error("no action given (provide --submit or --output)") if opts.machine_name is None: parser.error("--machine-name is required") if opts.run_order is None: parser.error("--run-order is required") # Load the results data. results_path, = args with open(results_path) as f: data = json.load(f) # Compute some data not present in the 'lit' report. machine_name = opts.machine_name run_order = str(opts.run_order) # Estimate the end time as being now, and the start time as being that minus # the elapsed testing time. utcnow = datetime.datetime.utcnow() start_time = utcnow - datetime.timedelta(seconds=data['elapsed']) end_time = utcnow # Create the LNT report format. lnt_results = {} lnt_results['Machine'] = { 'Name' : machine_name, 'Info' : { 'hardware' : capture(["uname","-m"], include_stderr=True).strip(), 'name' : capture(["uname","-n"], include_stderr=True).strip(), 'os' : capture(["uname","-sr"], include_stderr=True).strip(), 'uname' : capture(["uname","-a"], include_stderr=True).strip(), } } # FIXME: Record source versions for LLVM, Swift, etc.? lnt_results['Run'] = { 'Start Time' : start_time.strftime('%Y-%m-%d %H:%M:%S'), 'End Time' : end_time.strftime('%Y-%m-%d %H:%M:%S'), 'Info' : { '__report_version__' : '1', 'tag' : 'nts', 'inferred_run_order' : run_order, 'run_order' : run_order, 'sw_vers' : capture(['sw_vers'], include_stderr=True).strip(), } } lnt_results['Tests'] = lnt_tests = [] for test in data['tests']: # Ignore tests which have unexpected status. code = test['code'] if code not in ('PASS', 'XPASS', 'FAIL', 'XFAIL'): sys.stderr.write("ignoring test %r with result code %r" % ( test['name'], code)) continue # Extract the test name, which is encoded as 'suite :: name'. test_name = 'nts.%s' % (test['name'].split('::',1)[1][1:],) # Convert this test to the 'nts' schema. compile_success = test['metrics'].get('compile_success', 1) compile_time = test['metrics']['compile_time'] exec_success = test['metrics'].get('exec_success', 1) exec_time = test['metrics']['exec_time'] # FIXME: Ensure the test success flags matches the result code. # FIXME: The XFAIL handling here isn't going to be right. if not compile_success: lnt_tests.append({ 'Name' : '%s.compile.status' % (test_name,), 'Info' : {}, 'Data' : [ FAIL ] }) if not exec_success: lnt_tests.append({ 'Name' : '%s.exec.status' % (test_name,), 'Info' : {}, 'Data' : [ FAIL ] }) lnt_tests.append({ 'Name' : '%s.compile' % (test_name,), 'Info' : {}, 'Data' : [ compile_time ] }) lnt_tests.append({ 'Name' : '%s.exec' % (test_name,), 'Info' : {}, 'Data' : [ exec_time ] }) # Create the report data. lnt_result_data = json.dumps(lnt_results, indent=2) + '\n' # Write the results, if requested. if opts.output: sys.stderr.write('%s: generating report: %r\n' % ( timestamp(), opts.output)) with open(opts.output, 'w') as f: f.write(lnt_result_data) # Submit the results to an LNT server, if requested. if opts.submit_url: submit_results_to_server(lnt_result_data, opts.submit_url) if __name__ == '__main__': main()