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.

276 lines
11 KiB

2 years ago
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()