You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
11 KiB
275 lines
11 KiB
import sys
|
|
import os
|
|
import json
|
|
import pathlib
|
|
import argparse
|
|
import subprocess
|
|
import re
|
|
import platform
|
|
|
|
import numpy
|
|
import pandas
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
def get_time_stamp():
|
|
return datetime.now().strftime("%Y-%b-%d-%H-%M-%S")
|
|
|
|
|
|
def get_machine_info():
|
|
if platform.system() == "Windows":
|
|
return "Windows machine info not implemented"
|
|
if platform.system() == "Darwin":
|
|
core_count = int(subprocess.run(
|
|
["sysctl", "-n", "machdep.cpu.core_count"],
|
|
capture_output=True, text=True).stdout.strip())
|
|
brand_string = subprocess.run(
|
|
["sysctl", "-n", "machdep.cpu.brand_string"],
|
|
capture_output=True, text=True).stdout.strip()
|
|
memsize = int(subprocess.run(
|
|
["sysctl", "-n", "hw.memsize"],
|
|
capture_output=True, text=True).stdout.strip()) / 1024**3
|
|
return f"{core_count:d}-core {brand_string}, {memsize} GB memory"
|
|
if platform.system() == "Linux":
|
|
lscpu = subprocess.run(
|
|
["lscpu"], capture_output=True, text=True).stdout
|
|
cores_per_socket = int(
|
|
re.search(r"Core\(s\) per socket:\s*(\d*)", lscpu).group(1))
|
|
sockets = int(
|
|
re.search(r"Socket\(s\):\s*(\d*)", lscpu).group(1))
|
|
cpu_freq = (
|
|
float(re.search(r"CPU max MHz:\s*(.+)", lscpu).group(1)) / 1000)
|
|
model_name = re.search(r"Model name:\s*(.+)", lscpu).group(1)
|
|
meminfo = subprocess.run(
|
|
["cat", "/proc/meminfo"], capture_output=True, text=True).stdout
|
|
memsize = (
|
|
int(re.search(r"MemTotal:\s*(\d*) kB", meminfo).group(1)) / 1024**2)
|
|
return ((f"{sockets}x" if sockets > 1 else "")
|
|
+ f"{cores_per_socket}-core {cpu_freq} GHz {model_name}, {memsize:.1f} GB memory")
|
|
return ""
|
|
|
|
|
|
# A re equvalent to %g used in scanf
|
|
percent_g = "[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?"
|
|
|
|
|
|
def fixture_dir():
|
|
return pathlib.Path(__file__).parents[1] / "fixtures"
|
|
|
|
|
|
def sim_exe_name():
|
|
return "rigid_ipc_sim"
|
|
|
|
|
|
def find_sim_exe():
|
|
for build_dir in (pathlib.Path("."), pathlib.Path(__file__).parents[1] / "build"):
|
|
for sub_dir in "", "release", "debug":
|
|
sim_exe = build_dir / sub_dir / sim_exe_name()
|
|
if sim_exe.is_file():
|
|
return sim_exe.resolve()
|
|
return None
|
|
|
|
|
|
def get_git_hash():
|
|
return subprocess.run(
|
|
"git rev-parse HEAD".split(), capture_output=True,
|
|
cwd=pathlib.Path(__file__).parent, text=True).stdout.strip()
|
|
|
|
|
|
def get_remote_storage():
|
|
for remote_name in "google-drive", "nyu-gdrive", None:
|
|
if remote_name is None:
|
|
print("Unable to find remote storage using rclone! "
|
|
"Videos will not be uploaded")
|
|
return None
|
|
r = subprocess.run(["rclone", "about", f"{remote_name}:"],
|
|
capture_output=True, text=True)
|
|
print(r.stdout)
|
|
print(r.stderr)
|
|
if (r.stderr.strip() == ""):
|
|
break
|
|
return f"{remote_name}:rigid-ipc/benchmark/"
|
|
|
|
|
|
def create_parser():
|
|
parser = argparse.ArgumentParser(
|
|
description="Run all scenes and save a CSV of the results.")
|
|
parser.add_argument(
|
|
"--sim-exe", metavar=f"path/to/{sim_exe_name()}", type=pathlib.Path,
|
|
default=None, help="path to simulation executable")
|
|
parser.add_argument(
|
|
"-i", "--input", metavar="path/to/input", type=pathlib.Path,
|
|
dest="input", default=None, help="path to input json(s)", nargs="+")
|
|
parser.add_argument(
|
|
"-o", "--output", metavar="path/to/output.csv", type=pathlib.Path,
|
|
dest="output", default=None, help="path to output CSV")
|
|
parser.add_argument(
|
|
"--no-video", action="store_true", default=False,
|
|
help="do not render a video of the sim")
|
|
parser.add_argument(
|
|
"--loglevel", default=3, type=int, choices=range(7),
|
|
help="set log level 0=trace, 1=debug, 2=info, 3=warn, 4=error, 5=critical, 6=off")
|
|
parser.add_argument(
|
|
"--sim-args", default="", help=f"arguments to {sim_exe_name()}")
|
|
return parser
|
|
|
|
|
|
def parse_arguments():
|
|
parser = create_parser()
|
|
args = parser.parse_args()
|
|
if args.sim_exe is None:
|
|
args.sim_exe = find_sim_exe()
|
|
if args.sim_exe is None:
|
|
parser.exit(1, f"Unable to find {sim_exe_name()}\n")
|
|
else:
|
|
print(f"Using {args.sim_exe}")
|
|
if args.input is None:
|
|
args.input = [fixture_dir() / "3D" / "simple"]
|
|
input_jsons = []
|
|
for input_file in args.input:
|
|
if input_file.is_file() and input_file.suffix == ".json":
|
|
input_jsons.append(input_file.resolve())
|
|
elif input_file.is_dir():
|
|
input_jsons.extend(list(input_file.glob('**/*.json')))
|
|
args.input = input_jsons
|
|
if args.output is None:
|
|
args.output = pathlib.Path("benchmark.csv")
|
|
return args
|
|
|
|
|
|
def main():
|
|
args = parse_arguments()
|
|
machine_info = get_machine_info()
|
|
base_dir = fixture_dir().resolve()
|
|
remote_storage = get_remote_storage()
|
|
|
|
df = pandas.DataFrame(columns=[
|
|
"scene", "dim", "num_bodies", "num_vertices", "num_edges", "num_faces",
|
|
"timestep", "num_timesteps", "dhat", "mu", "eps_v",
|
|
"friction_iterations", "cor", "eps_d", "avg_num_contacts",
|
|
"max_num_contacts", "avg_step_time", "max_step_time", "ccd_broad_phase",
|
|
"ccd_narrow_phase", "distance_broad_phase", "avg_solver_iterations",
|
|
"max_solver_iterations", "video", "machine", "max_threads", "memory", "git_hash",
|
|
"notes"])
|
|
|
|
max_threads = 16
|
|
|
|
for scene in args.input:
|
|
print(f"Running {scene}")
|
|
|
|
git_hash = get_git_hash()
|
|
|
|
try:
|
|
scene_name = scene.resolve().relative_to(base_dir)
|
|
scene_name = str(scene_name.parent / scene_name.stem)
|
|
except ValueError:
|
|
scene_name = scene.stem
|
|
|
|
sim_output_dir = pathlib.Path("output") / scene_name
|
|
sim_output_dir.mkdir(parents=True, exist_ok=True)
|
|
# with open(sim_output_dir / "log.txt", 'w') as log_file:
|
|
# sim = subprocess.Popen(
|
|
# [str(args.sim_exe), scene.resolve(),
|
|
# sim_output_dir, "--loglevel", str(args.loglevel)],
|
|
# stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
|
# for line in sim.stdout:
|
|
# sys.stdout.write(line)
|
|
# log_file.write(line)
|
|
# sim.wait()
|
|
subprocess.run([str(args.sim_exe), "--ngui", str(scene.resolve()),
|
|
str(sim_output_dir), "--loglevel", str(args.loglevel),
|
|
"--nthreads", str(max_threads)]
|
|
+ args.sim_args.split())
|
|
|
|
if(args.no_video):
|
|
video_url = "N/a"
|
|
else:
|
|
print("Rendering simulation")
|
|
video_name = f"{scene.stem}-{get_time_stamp()}.mp4"
|
|
subprocess.run([str(args.sim_exe.parent / "tools" / "render_simulation"),
|
|
sim_output_dir / "sim.json",
|
|
"-o", sim_output_dir / video_name,
|
|
"--loglevel", str(args.loglevel)])
|
|
if remote_storage is not None:
|
|
remote_path = (
|
|
f"{remote_storage}{pathlib.Path(scene_name).parent}")
|
|
subprocess.run(
|
|
["rclone", "copy", sim_output_dir / video_name,
|
|
remote_path])
|
|
video_url = subprocess.run(
|
|
["rclone", "link", f"{remote_path}/{video_name}"],
|
|
capture_output=True, text=True).stdout.strip()
|
|
print(f"Uploaded video to {video_url}")
|
|
|
|
# subprocess.run([str(args.sim_exe.parent / "tools" / "obj_sequence"),
|
|
# "-i", sim_output_dir / "sim.json",
|
|
# "-o", sim_output_dir / "objs",
|
|
# "--loglevel", str(args.loglevel)])
|
|
|
|
with open(sim_output_dir / "sim.json") as sim_output:
|
|
sim_json = json.load(sim_output)
|
|
sim_args = sim_json["args"]
|
|
sim_stats = sim_json["stats"]
|
|
|
|
log_dirs = list(filter(lambda p: p.is_dir(),
|
|
sim_output_dir.glob("log*")))
|
|
if log_dirs:
|
|
profiler_dir = max(log_dirs, key=os.path.getmtime)
|
|
profiler_df = pandas.read_csv(
|
|
profiler_dir / "summary.csv", header=1, index_col=0,
|
|
skipinitialspace=True, converters={
|
|
"percentage_time": lambda x: float(x.strip('%'))})
|
|
broad_ccd = profiler_df.percentage_time.get(
|
|
"detect_continuous_collision_candidates_rigid", -1)
|
|
narrow_ccd = profiler_df.percentage_time.get(
|
|
"DistanceBarrierConstraint::compute_earliest_toi_narrow_phase",
|
|
-1)
|
|
broad_distance = profiler_df.percentage_time.get(
|
|
"DistanceBarrierConstraint::construct_constraint_set", -1)
|
|
else:
|
|
print("Profiling not enabled")
|
|
broad_ccd = -1
|
|
narrow_ccd = -1
|
|
broad_distance = -1
|
|
|
|
df_row = {
|
|
"scene": scene_name,
|
|
"dim": sim_stats["dim"],
|
|
"num_bodies": sim_stats["num_bodies"],
|
|
"num_vertices": sim_stats["num_vertices"],
|
|
"num_edges": sim_stats["num_edges"],
|
|
"num_faces": sim_stats["num_faces"],
|
|
"timestep": sim_args["timestep"],
|
|
"num_timesteps": sim_stats["num_timesteps"],
|
|
"dhat": sim_args["distance_barrier_constraint"]["initial_barrier_activation_distance"],
|
|
"mu": sim_args["rigid_body_problem"]["coefficient_friction"],
|
|
"eps_v": sim_args["friction_constraints"]["static_friction_speed_bound"],
|
|
"friction_iterations": sim_args["friction_constraints"]["iterations"],
|
|
"cor": sim_args["rigid_body_problem"]["coefficient_restitution"],
|
|
"eps_d": sim_args["ipc_solver"]["velocity_conv_tol"],
|
|
"avg_num_contacts": numpy.average(sim_stats["num_contacts"]),
|
|
"max_num_contacts": max(sim_stats["num_contacts"]),
|
|
"avg_step_time": numpy.average(sim_stats["step_timings"]),
|
|
"max_step_time": max(sim_stats["step_timings"]),
|
|
"ccd_broad_phase": f"{broad_ccd:g}%",
|
|
"ccd_narrow_phase": f"{narrow_ccd:g}%",
|
|
"distance_broad_phase": f"{broad_distance:g}%",
|
|
"avg_solver_iterations": numpy.average(sim_stats["solver_iterations"]),
|
|
"max_solver_iterations": max(sim_stats["solver_iterations"]),
|
|
"video": video_url,
|
|
"machine": machine_info,
|
|
"max_threads": max_threads,
|
|
"memory": sim_stats["memory"] / 1024**2,
|
|
"git_hash": git_hash,
|
|
"notes": "" # results.stdout.strip()
|
|
}
|
|
|
|
df.loc[scene] = df_row
|
|
|
|
df.to_csv(args.output, index=False)
|
|
print(f"Results written to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|