|
|
|
@ -1,8 +1,12 @@
|
|
|
|
|
import argparse
|
|
|
|
|
import os
|
|
|
|
|
import json
|
|
|
|
|
import readline
|
|
|
|
|
import multiprocessing
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from time import strftime, localtime
|
|
|
|
|
from operator import itemgetter
|
|
|
|
|
|
|
|
|
|
from power_fv.build import sby
|
|
|
|
|
from power_fv.core import PowerFVCore
|
|
|
|
|
from power_fv.check import PowerFVCheck
|
|
|
|
@ -107,10 +111,19 @@ class PowerFVSession:
|
|
|
|
|
parser.set_defaults(_cmd=self.build)
|
|
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-j", "--jobs", type=int, default=os.cpu_count(),
|
|
|
|
|
"-j", "--jobs", type=int, default=multiprocessing.cpu_count(),
|
|
|
|
|
help="number of worker processes (default: %(default)s)")
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--result-dir", type=Path, default=None,
|
|
|
|
|
help="result directory (default: result-$(date +%%Y%%m%%d_%%H%%M%%S))")
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--build-dir", type=Path, default=Path("./build"),
|
|
|
|
|
help="output directory (default: %(default)s)")
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--connect-to", type=json.loads, required=False,
|
|
|
|
|
help="execute the build plan on a remote server using SSH "
|
|
|
|
|
"(JSON string of arguments passed to paramiko's SSHClient.connect)")
|
|
|
|
|
|
|
|
|
|
PowerFVCheck .add_build_arguments(parser)
|
|
|
|
|
self.core_cls.add_build_arguments(parser)
|
|
|
|
|
|
|
|
|
|
# Commands
|
|
|
|
@ -134,18 +147,80 @@ class PowerFVSession:
|
|
|
|
|
pprint(self._checks, sort_dicts=False)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _build_check(core_cls, check_name, check_args, build_args):
|
|
|
|
|
check_cls = PowerFVCheck.all_checks[tuple(check_name.split(":"))]
|
|
|
|
|
core = core_cls()
|
|
|
|
|
check = check_cls(core=core, **check_args)
|
|
|
|
|
check.build(**build_args)
|
|
|
|
|
def _build_worker(check_name, plan, root, connect_to):
|
|
|
|
|
if connect_to is not None:
|
|
|
|
|
products = plan.execute_remote_ssh(connect_to=connect_to, root=root)
|
|
|
|
|
else:
|
|
|
|
|
products = plan.execute_local(root)
|
|
|
|
|
return check_name, products
|
|
|
|
|
|
|
|
|
|
def build(self, *, jobs, result_dir, build_dir, connect_to, **kwargs):
|
|
|
|
|
worker_inputs = []
|
|
|
|
|
worker_outputs = None
|
|
|
|
|
|
|
|
|
|
# Create the result directory.
|
|
|
|
|
|
|
|
|
|
def build(self, *, jobs, **kwargs):
|
|
|
|
|
map_func = PowerFVSession._build_check
|
|
|
|
|
map_args = []
|
|
|
|
|
if result_dir is None:
|
|
|
|
|
result_dir = Path("./result-{}".format(strftime("%Y%m%d_%H%M%S", localtime())))
|
|
|
|
|
else:
|
|
|
|
|
result_dir = Path(result_dir)
|
|
|
|
|
|
|
|
|
|
Path.mkdir(result_dir, exist_ok=False)
|
|
|
|
|
|
|
|
|
|
# Create build plans for scheduled checks.
|
|
|
|
|
|
|
|
|
|
def prepare_check(check_name, check_args):
|
|
|
|
|
check_cls = PowerFVCheck.all_checks[tuple(check_name.split(":"))]
|
|
|
|
|
check = check_cls(core=self.core_cls(), **check_args)
|
|
|
|
|
return check.build(do_build=False, **kwargs)
|
|
|
|
|
|
|
|
|
|
for check_name, check_args in self._checks.items():
|
|
|
|
|
map_args.append((self.core_cls, check_name, check_args, kwargs))
|
|
|
|
|
plan = prepare_check(check_name, check_args)
|
|
|
|
|
root = str(build_dir / check_name)
|
|
|
|
|
worker_inputs.append((check_name, plan, root, connect_to))
|
|
|
|
|
|
|
|
|
|
# Save an archive of the build files to the result directory.
|
|
|
|
|
plan.archive(result_dir / f"{check_name}.zip")
|
|
|
|
|
|
|
|
|
|
# Execute build plans.
|
|
|
|
|
|
|
|
|
|
if connect_to is not None:
|
|
|
|
|
# BuildPlan.execute_remote_ssh() will fail if the parent of its root directory doesn't
|
|
|
|
|
# exist. In our case, checks are built in subdirectories of `build_dir`, so we need to
|
|
|
|
|
# create it beforehand.
|
|
|
|
|
from paramiko import SSHClient
|
|
|
|
|
with SSHClient() as client:
|
|
|
|
|
client.load_system_host_keys()
|
|
|
|
|
client.connect(**connect_to)
|
|
|
|
|
with client.open_sftp() as sftp:
|
|
|
|
|
try:
|
|
|
|
|
sftp.mkdir(str(build_dir))
|
|
|
|
|
except IOError as e:
|
|
|
|
|
if e.errno:
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
|
with multiprocessing.Pool(jobs) as pool:
|
|
|
|
|
pool.starmap(map_func, map_args)
|
|
|
|
|
worker_outputs = pool.starmap(PowerFVSession._build_worker, worker_inputs)
|
|
|
|
|
|
|
|
|
|
# Write the results.
|
|
|
|
|
|
|
|
|
|
def write_result(check_name, products):
|
|
|
|
|
status = "unknown"
|
|
|
|
|
for filename in ("PASS", "FAIL"):
|
|
|
|
|
try:
|
|
|
|
|
products.get(f"{check_name}_tb/{filename}")
|
|
|
|
|
status = filename.lower()
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
with open(result_dir / "status.txt", "a") as statusfile:
|
|
|
|
|
statusfile.write(f"{check_name} {status}\n")
|
|
|
|
|
|
|
|
|
|
if status == "fail":
|
|
|
|
|
with open(result_dir / f"{check_name}.log", "w") as logfile:
|
|
|
|
|
logfile.write(products.get(f"{check_name}_tb/engine_0/logfile.txt", "t"))
|
|
|
|
|
with open(result_dir / f"{check_name}.vcd", "w") as vcdfile:
|
|
|
|
|
vcdfile.write(products.get(f"{check_name}_tb/engine_0/trace.vcd", "t"))
|
|
|
|
|
|
|
|
|
|
for check_name, products in sorted(worker_outputs, key=itemgetter(0)):
|
|
|
|
|
write_result(check_name, products)
|
|
|
|
|