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()
							 | 
						|
								
							 |