|
|
@@ -4,6 +4,7 @@ import subprocess
|
|
|
import csv
|
|
|
import re
|
|
|
from argparse import ArgumentParser, Action
|
|
|
+from pathlib import Path
|
|
|
from itertools import product
|
|
|
from datetime import datetime
|
|
|
|
|
|
@@ -27,7 +28,7 @@ TIME_PATTERN = re.compile(
|
|
|
re.S
|
|
|
)
|
|
|
|
|
|
-def run_devstone(model_type, depth, width, mode="parallel", out=False, *, classic=False, number=1, gen_mode="uniform"):
|
|
|
+def run_devstone(model_type, depth, width, mode, out, memory, *, classic=False, number=1, gen_mode="uniform"):
|
|
|
"""Runs the devstone_driver.py script and returns timing results as floats."""
|
|
|
SCRIPT_PATH = PyPDEVS
|
|
|
if mode == "minimal":
|
|
|
@@ -49,27 +50,54 @@ def run_devstone(model_type, depth, width, mode="parallel", out=False, *, classi
|
|
|
cmd.extend(["-g", gen_mode])
|
|
|
if mode == "classic":
|
|
|
cmd.extend(["-C"])
|
|
|
-
|
|
|
- result = subprocess.run(cmd, capture_output=True, text=True)
|
|
|
- output = result.stdout.strip()
|
|
|
+ full_cmd = " ".join(cmd)
|
|
|
+
|
|
|
+ if memory:
|
|
|
+ Path("/".join(memory["file"].split("/")[0:-1])).mkdir(parents=True, exist_ok=True)
|
|
|
+ full_cmd = (
|
|
|
+ f"{full_cmd} & "
|
|
|
+ f"PID=$!; "
|
|
|
+ f"python -m procpath record -i {memory['interval']} -f stat -d {memory['file']} "
|
|
|
+ f"--stop-without-result \"$..children[?(@.stat.pid == $PID)]\""
|
|
|
+ )
|
|
|
+
|
|
|
+ result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True)
|
|
|
+ cout = result.stdout.strip()
|
|
|
if out:
|
|
|
- print(output)
|
|
|
+ print(cout)
|
|
|
|
|
|
- errors = result.stderr.strip()
|
|
|
- match = TIME_PATTERN.search(output)
|
|
|
+ cerr = result.stderr.strip()
|
|
|
+ match = TIME_PATTERN.search(cout)
|
|
|
|
|
|
if match:
|
|
|
creation, setup, sim = map(float, match.groups())
|
|
|
- return creation, setup, sim, output
|
|
|
+ return creation, setup, sim
|
|
|
|
|
|
print("Failed to run: ", cmd)
|
|
|
- print(output)
|
|
|
- print(errors)
|
|
|
- return None, None, None, output
|
|
|
+ print(cout)
|
|
|
+ print(cerr)
|
|
|
+ return None, None, None
|
|
|
+
|
|
|
+def defaultArguments(parser):
|
|
|
+ parser.add_argument("-p", "--path", type=str, default="./Results-PyPDEVS", help="Path to the results directory")
|
|
|
+ parser.add_argument("-f", '--filename', type=str, default="", help="Name of the output file")
|
|
|
+ parser.add_argument("-a", "--append", action="store_true", help="Append to the output file instead of overwriting.")
|
|
|
+
|
|
|
+ parser.add_argument("-md", "--max-depth", default=8, type=int, help="Max depth for the generated models")
|
|
|
+ parser.add_argument("-ds", "--depth-stepsize", type=int, help="Determines for which depths a model is generated.")
|
|
|
+
|
|
|
+ parser.add_argument("-mw", "--max-width", default=8, type=int, help="Max width for the generated models")
|
|
|
+ parser.add_argument("-ws", "--width-stepsize", type=int, help="Determines for which depths a model is generated.")
|
|
|
+
|
|
|
+ parser.add_argument("-r", "--repetitions", default=1, type=int, help="The number of times a simulation is repeated")
|
|
|
+ parser.add_argument("-O", "--output", action="store_true", help="Print Simulation Outputs")
|
|
|
+ parser.add_argument("-M", "--memory", action="store_true", help="Measures the memory usage of a simulation.")
|
|
|
+ parser.add_argument("-i", "--interval", default=1.0, type=float, help="The interval at which measurements are taken.")
|
|
|
|
|
|
def Minimal(subparsers):
|
|
|
minimal = subparsers.add_parser("minimal", help="Minimal DEVS kernel")
|
|
|
minimal.set_defaults(mode="minimal")
|
|
|
+ defaultArguments(minimal)
|
|
|
minimal.add_argument(
|
|
|
"-ms", "--model-structure",
|
|
|
nargs="+",
|
|
|
@@ -80,6 +108,7 @@ def Minimal(subparsers):
|
|
|
def Classic(subparsers):
|
|
|
classic = subparsers.add_parser("classic", help="Classic DEVS Simulator")
|
|
|
classic.set_defaults(mode="classic")
|
|
|
+ defaultArguments(classic)
|
|
|
classic.add_argument(
|
|
|
"-ms", "--model-structure",
|
|
|
nargs="+",
|
|
|
@@ -90,6 +119,7 @@ def Classic(subparsers):
|
|
|
def Parallel(subparsers):
|
|
|
parallel = subparsers.add_parser("parallel", help="Parallel DEVS")
|
|
|
parallel.set_defaults(mode="parallel")
|
|
|
+ defaultArguments(parallel)
|
|
|
parallel.add_argument(
|
|
|
"-ms", "--model-structure",
|
|
|
nargs="+",
|
|
|
@@ -100,6 +130,7 @@ def Parallel(subparsers):
|
|
|
def Dynamic(subparsers):
|
|
|
dynamic = subparsers.add_parser("dynamic", help="Dynamic Structure DEVS")
|
|
|
dynamic.set_defaults(mode="dynamic")
|
|
|
+ defaultArguments(dynamic)
|
|
|
dynamic.add_argument(
|
|
|
"-ms", "--model-structure",
|
|
|
nargs="+",
|
|
|
@@ -125,21 +156,6 @@ def Dynamic(subparsers):
|
|
|
|
|
|
def parse_args():
|
|
|
parser = ArgumentParser()
|
|
|
- parser.add_argument("-p", "--path", type=str, default="./Results-PyPDEVS/", help="Path to the results directory")
|
|
|
- parser.add_argument("-f", '--filename', type=str, default="", help="Name of the output file")
|
|
|
- parser.add_argument("-a", "--append", action="store_true", help="Append to the output file instead of overwriting.")
|
|
|
-
|
|
|
- parser.add_argument("-md", "--max-depth", default=8, type=int, help="Max depth for the generated models")
|
|
|
- parser.add_argument("-ds", "--depth-stepsize", type=int,
|
|
|
- help="Determines for which depths a model is generated.")
|
|
|
-
|
|
|
- parser.add_argument("-mw", "--max-width", default=8, type=int, help="Max width for the generated models")
|
|
|
- parser.add_argument("-ws", "--width-stepsize", type=int,
|
|
|
- help="Determines for which depths a model is generated.")
|
|
|
-
|
|
|
- parser.add_argument("-r", "--repetitions", default=1, type=int, help="The number of times a simulation is repeated")
|
|
|
- parser.add_argument("-O", "--output", action="store_true", help="Print Simulation Outputs")
|
|
|
-
|
|
|
subparsers = parser.add_subparsers(dest="mode")
|
|
|
Minimal(subparsers)
|
|
|
Parallel(subparsers)
|
|
|
@@ -154,6 +170,31 @@ def parse_args():
|
|
|
|
|
|
return args
|
|
|
|
|
|
+def run(writer, csvfile, model_type, d, w, args, mem_cmd, extra):
|
|
|
+ """Orchestrates the repeated simulation runs."""
|
|
|
+ for depth in range(d[1], d[0] + 1, d[1]):
|
|
|
+ for width in range(w[1], w[0] + 1, w[1]):
|
|
|
+ for run in range(args.repetitions):
|
|
|
+ creation, setup, sim = run_devstone(
|
|
|
+ model_type, depth, width,
|
|
|
+ args.mode, args.output,
|
|
|
+ mem_cmd,
|
|
|
+ **extra
|
|
|
+ )
|
|
|
+
|
|
|
+ # Only write to CSV if a writer was provided
|
|
|
+ if writer and creation is not None:
|
|
|
+ row = {
|
|
|
+ "model_type": model_type, "depth": depth, "width": width, "run": run,
|
|
|
+ "creation_time": creation, "setup_time": setup, "simulation_time": sim,
|
|
|
+ }
|
|
|
+ if args.mode == "dynamic":
|
|
|
+ row["number"] = args.number
|
|
|
+ row["generator_mode"] = args.gen_mode
|
|
|
+ writer.writerow(row)
|
|
|
+ csvfile.flush()
|
|
|
+
|
|
|
+
|
|
|
if __name__ == "__main__":
|
|
|
args = parse_args()
|
|
|
print(args)
|
|
|
@@ -167,7 +208,10 @@ if __name__ == "__main__":
|
|
|
|
|
|
fieldnames = ["model_type", "depth", "width", "run",
|
|
|
"creation_time", "setup_time", "simulation_time"]
|
|
|
- if args.mode == "dynamic":
|
|
|
+ mode = args.mode
|
|
|
+ if mode == "dynamic":
|
|
|
+ if args.classic:
|
|
|
+ mode += "-classic"
|
|
|
fieldnames += ["number", "generator_mode"]
|
|
|
|
|
|
extra = {
|
|
|
@@ -177,41 +221,32 @@ if __name__ == "__main__":
|
|
|
}
|
|
|
|
|
|
for model_type in args.model_structure:
|
|
|
- model_filename = f"{args.path}{model_type}"
|
|
|
+ model_time_file = f"{args.path}/Time/{mode}/{model_type}"
|
|
|
+ model_memory_file = f"{args.path}/Memory/{mode}/{model_type}"
|
|
|
+
|
|
|
if args.filename:
|
|
|
- model_filename += f"_{args.filename}"
|
|
|
- model_filename += ".csv"
|
|
|
-
|
|
|
- file_mode = "a" if args.append else "w"
|
|
|
- write_header = not args.append or not os.path.exists(model_filename)
|
|
|
- with open(model_filename, file_mode, newline="") as csvfile:
|
|
|
- writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
|
- if write_header:
|
|
|
- writer.writeheader()
|
|
|
-
|
|
|
- for depth in range(d[1], d[0] + 1, d[1]):
|
|
|
- for width in range(w[1], w[0] + 1, w[1]):
|
|
|
- for run in range(args.repetitions):
|
|
|
- creation, setup, sim, output = run_devstone(
|
|
|
- model_type, depth, width,
|
|
|
- args.mode, args.output,
|
|
|
- **extra
|
|
|
- )
|
|
|
-
|
|
|
- if creation is not None:
|
|
|
- row = {
|
|
|
- "model_type": model_type,
|
|
|
- "depth": depth,
|
|
|
- "width": width,
|
|
|
- "run": run,
|
|
|
- "creation_time": creation,
|
|
|
- "setup_time": setup,
|
|
|
- "simulation_time": sim,
|
|
|
- }
|
|
|
- if args.mode == "dynamic":
|
|
|
- row["number"] = args.number
|
|
|
- row["generator_mode"] = args.gen_mode
|
|
|
- writer.writerow(row)
|
|
|
- csvfile.flush()
|
|
|
- print(f"✅ Saved {model_type} results to {model_filename}")
|
|
|
+ suffix = f"_{args.filename}"
|
|
|
+ model_time_file += suffix
|
|
|
+ model_memory_file += suffix
|
|
|
+
|
|
|
+ model_time_file += ".csv"
|
|
|
+ model_memory_file += ".sqlite"
|
|
|
+
|
|
|
+ mem_cmd = {"interval": args.interval, "file": model_memory_file} if args.memory else {}
|
|
|
+
|
|
|
+ if not args.memory:
|
|
|
+ file_mode = "a" if args.append else "w"
|
|
|
+ write_header = not args.append or not os.path.exists(model_time_file)
|
|
|
+
|
|
|
+ with open(model_time_file, file_mode, newline="") as csvfile:
|
|
|
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
|
+ if write_header:
|
|
|
+ writer.writeheader()
|
|
|
+
|
|
|
+ run(writer, csvfile, model_type, d, w, args, mem_cmd, extra)
|
|
|
+ print(f"✅ Saved timing results to {model_time_file}")
|
|
|
+ else:
|
|
|
+ print("📊 Measuring memory (Timing results will not be saved)...")
|
|
|
+ run(None, None, model_type, d, w, args, mem_cmd, extra)
|
|
|
+ print(f"✅ Saved memory database to {model_memory_file}")
|
|
|
|