xref: /aosp_15_r20/art/tools/parallel_run.py (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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