1*795d594fSAndroid Build Coastguard Worker#!/usr/bin/python3 2*795d594fSAndroid Build Coastguard Worker# 3*795d594fSAndroid Build Coastguard Worker# Copyright 2019, The Android Open Source Project 4*795d594fSAndroid Build Coastguard Worker# 5*795d594fSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*795d594fSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*795d594fSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*795d594fSAndroid Build Coastguard Worker# 9*795d594fSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*795d594fSAndroid Build Coastguard Worker# 11*795d594fSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*795d594fSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*795d594fSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*795d594fSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*795d594fSAndroid Build Coastguard Worker# limitations under the License. 16*795d594fSAndroid Build Coastguard Worker 17*795d594fSAndroid Build Coastguard Worker""" 18*795d594fSAndroid Build Coastguard WorkerRun a command using multiple cores in parallel. Stop when one exits zero and save the log from 19*795d594fSAndroid Build Coastguard Workerthat run. 20*795d594fSAndroid Build Coastguard Worker""" 21*795d594fSAndroid Build Coastguard Worker 22*795d594fSAndroid Build Coastguard Workerimport argparse 23*795d594fSAndroid Build Coastguard Workerimport concurrent.futures 24*795d594fSAndroid Build Coastguard Workerimport contextlib 25*795d594fSAndroid Build Coastguard Workerimport itertools 26*795d594fSAndroid Build Coastguard Workerimport os 27*795d594fSAndroid Build Coastguard Workerimport os.path 28*795d594fSAndroid Build Coastguard Workerimport shutil 29*795d594fSAndroid Build Coastguard Workerimport subprocess 30*795d594fSAndroid Build Coastguard Workerimport tempfile 31*795d594fSAndroid Build Coastguard Worker 32*795d594fSAndroid Build Coastguard Worker 33*795d594fSAndroid Build Coastguard Workerdef run_one(cmd, tmpfile): 34*795d594fSAndroid Build Coastguard Worker """Run the command and log result to tmpfile. Return both the file name and returncode.""" 35*795d594fSAndroid Build Coastguard Worker with open(tmpfile, "x") as fd: 36*795d594fSAndroid Build Coastguard Worker return tmpfile, subprocess.run(cmd, stdout=fd).returncode 37*795d594fSAndroid Build Coastguard Worker 38*795d594fSAndroid Build Coastguard Workerdef main(): 39*795d594fSAndroid Build Coastguard Worker parser = argparse.ArgumentParser( 40*795d594fSAndroid Build Coastguard Worker description="""Run a command using multiple cores and save non-zero exit log 41*795d594fSAndroid Build Coastguard Worker 42*795d594fSAndroid Build Coastguard Worker The cmd should print all output to stdout. Stderr is not captured.""" 43*795d594fSAndroid Build Coastguard Worker ) 44*795d594fSAndroid Build Coastguard Worker parser.add_argument("--jobs", "-j", type=int, help="max number of jobs. default 60", default=60) 45*795d594fSAndroid Build Coastguard Worker parser.add_argument("cmd", help="command to run") 46*795d594fSAndroid Build Coastguard Worker parser.add_argument("--out", type=str, help="where to put result", default="out_log") 47*795d594fSAndroid Build Coastguard Worker args = parser.parse_args() 48*795d594fSAndroid Build Coastguard Worker cnt = 0 49*795d594fSAndroid Build Coastguard Worker found_fail = False 50*795d594fSAndroid Build Coastguard Worker ids = itertools.count(0) 51*795d594fSAndroid Build Coastguard Worker with tempfile.TemporaryDirectory() as td: 52*795d594fSAndroid Build Coastguard Worker print("Temporary files in {}".format(td)) 53*795d594fSAndroid Build Coastguard Worker with concurrent.futures.ProcessPoolExecutor(args.jobs) as p: 54*795d594fSAndroid Build Coastguard Worker fs = set() 55*795d594fSAndroid Build Coastguard Worker while len(fs) != 0 or not found_fail: 56*795d594fSAndroid Build Coastguard Worker if not found_fail: 57*795d594fSAndroid Build Coastguard Worker for _, idx in zip(range(args.jobs - len(fs)), ids): 58*795d594fSAndroid Build Coastguard Worker fs.add(p.submit(run_one, args.cmd, os.path.join(td, "run_log." + str(idx)))) 59*795d594fSAndroid Build Coastguard Worker ws = concurrent.futures.wait(fs, return_when=concurrent.futures.FIRST_COMPLETED) 60*795d594fSAndroid Build Coastguard Worker fs = ws.not_done 61*795d594fSAndroid Build Coastguard Worker done = list(map(lambda a: a.result(), ws.done)) 62*795d594fSAndroid Build Coastguard Worker cnt += len(done) 63*795d594fSAndroid Build Coastguard Worker print("\r{} runs".format(cnt), end="") 64*795d594fSAndroid Build Coastguard Worker failed = [d for d,r in done if r != 0] 65*795d594fSAndroid Build Coastguard Worker succ = [d for d,r in done if r == 0] 66*795d594fSAndroid Build Coastguard Worker for f in succ: 67*795d594fSAndroid Build Coastguard Worker os.remove(f) 68*795d594fSAndroid Build Coastguard Worker if len(failed) != 0: 69*795d594fSAndroid Build Coastguard Worker if not found_fail: 70*795d594fSAndroid Build Coastguard Worker found_fail = True 71*795d594fSAndroid Build Coastguard Worker print("\rFailed at {} runs".format(cnt)) 72*795d594fSAndroid Build Coastguard Worker if len(failed) != 1: 73*795d594fSAndroid Build Coastguard Worker for f,i in zip(failed, range(len(failed))): 74*795d594fSAndroid Build Coastguard Worker shutil.copyfile(f, args.out+"."+str(i)) 75*795d594fSAndroid Build Coastguard Worker else: 76*795d594fSAndroid Build Coastguard Worker shutil.copyfile(failed[0], args.out) 77*795d594fSAndroid Build Coastguard Worker 78*795d594fSAndroid Build Coastguard Workerif __name__ == '__main__': 79*795d594fSAndroid Build Coastguard Worker main() 80