devstone_comparative.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. from __future__ import annotations
  2. import argparse
  3. import csv
  4. import re
  5. import subprocess
  6. import sys
  7. import time
  8. DEFAULT_PARAMS = ((300, 10, 0, 0), (10, 300, 0, 0), (300, 300, 0, 0))
  9. DEFAULT_MODEL_TYPES = ("LI", "HI", "HO", "HOmod")
  10. DEFAULT_MAX_TIME = 1e10
  11. DEFAULT_NUM_REPS = 30
  12. COMMANDS = {
  13. "adevs": "devstone/adevs/bin/devstone {model_type} {width} {depth} {int_cycles} {ext_cycles}",
  14. "cadmium": {
  15. "v1": "devstone/cadmium/build/cadmium-dynamic-devstone --kind={model_type} --width={width} --depth={depth} --int-cycles={int_cycles} --ext-cycles={ext_cycles}",
  16. "v2": {
  17. "sequential": "simulators/cadmium_v2/bin/main_devstone {model_type} {width} {depth} {int_cycles} {ext_cycles}",
  18. "parallel": "simulators/cadmium_v2/bin/parallel_main_devstone {model_type} {width} {depth} {int_cycles} {ext_cycles}",
  19. },
  20. },
  21. "pypdevs": {
  22. "standard": {
  23. "python": "python3 devstone/pythonpdevs/main.py -m {model_type} -w {width} -d {depth} -i {int_cycles} -e {ext_cycles}",
  24. "pypy": "pypy3 devstone/pythonpdevs/main.py -m {model_type} -w {width} -d {depth} -i {int_cycles} -e {ext_cycles}",
  25. },
  26. "minimal": {
  27. "python": "python3 devstone/pythonpdevs-minimal/main.py -m {model_type} -d {depth} -w {width} -i {int_cycles} -e {ext_cycles}",
  28. "pypy": "pypy3 devstone/pythonpdevs-minimal/main.py -m {model_type} -d {depth} -w {width} -i {int_cycles} -e {ext_cycles}",
  29. },
  30. },
  31. "xdevs": {
  32. "c": "simulators/xdevs.c/examples/devstone/devstone -w {width} -d {depth} -b {model_type} -m 1",
  33. "cpp": "simulators/xdevs.cpp/src/xdevs/examples/DevStone/DevStone -w {width} -d {depth} -b {model_type} -m 1",
  34. # "go": , # TODO add this
  35. "java": {
  36. "sequential": "java -cp simulators/xdevs.java/target/xdevs-2.0.3-jar-with-dependencies.jar xdevs.lib.performance.DevStoneSimulation --model={model_type} --width={width} --depth={depth} --delay-distribution=Constant-{int_cycles} --coordinator=Coordinator",
  37. "parallel": "java -cp simulators/xdevs.java/target/xdevs-2.0.3-jar-with-dependencies.jar xdevs.lib.performance.DevStoneSimulation --model={model_type} --width={width} --depth={depth} --delay-distribution=Constant-{int_cycles} --coordinator=CoordinatorParallel",
  38. },
  39. "py": "python3 simulators/xdevs.py/xdevs/examples/devstone/devstone.py {model_type} {width} {depth} {int_cycles} {ext_cycles}",
  40. "rs": "simulators/xdevs.rs/target/release/xdevs {model_type} {width} {depth} {int_cycles} {ext_cycles}",
  41. },
  42. }
  43. DEFAULT_RE = r"model creation time(?:[ \(s\)]*): ?([0-9.e-]+)([ nuµms]*).*(?:engine|simulator) (?:set ?up|creation) time(?:[ \(s\)]*): ?([0-9.e-]+)([ nuµms]*).*simulation time(?:[ \(s\)]*): ?([0-9.e-]+)([ nuµms]*)"
  44. CUSTOM_RE = {}
  45. def serialize_simengines(res: list[str], prefix: str, flavors: str | dict):
  46. if isinstance(flavors, str):
  47. res.append(prefix)
  48. else:
  49. for key, val in flavors.items():
  50. new_prefix = f'{prefix}-{key}' if prefix else key
  51. serialize_simengines(res, new_prefix, val)
  52. def parse_args():
  53. parser = argparse.ArgumentParser(description='Script to compare DEVStone implementations with different engines')
  54. parser.add_argument('-m', '--model-types', help='DEVStone model type (LI, HI, HO, HOmod)')
  55. parser.add_argument('-w', '--width', type=int, help='Width of each coupled model.')
  56. parser.add_argument('-d', '--depth', type=int, help='Number of recursive levels of the model.')
  57. parser.add_argument('-i', '--int-cycles', type=int, help='Dhrystone cycles executed in internal transitions')
  58. parser.add_argument('-e', '--ext-cycles', type=int, help='Dhrystone cycles executed in external transitions')
  59. parser.add_argument('-n', '--num-rep', type=int, help='Number of repetitions per each engine and configuration')
  60. parser.add_argument('-a', '--include_engines', help='Add specific engines to perform the comparative')
  61. parser.add_argument('-r', '--exclude_engines', help='Exclude specific engines from the comparative')
  62. parser.add_argument('-o', '--out-file', help='Output file path')
  63. parser.add_argument('-p', '--params', help='Specify params in a condensed form: d1-w1-ic1-ec1, d2-w2-ic2-ec2...')
  64. args = parser.parse_args()
  65. if args.model_types:
  66. args.model_types = [x.strip() for x in args.model_types.split(",")]
  67. else:
  68. args.model_types = list(DEFAULT_MODEL_TYPES)
  69. params = []
  70. if args.params:
  71. params = [x.strip().split("-") for x in args.params.split(",")]
  72. # args.params = [x.strip().split("-") for x in args.params.split(",")]
  73. elif args.depth and args.width:
  74. int_cycles = args.int_cycles or 0
  75. ext_cycles = args.ext_cycles or 0
  76. params = [(args.depth, args.width, int_cycles, ext_cycles)]
  77. # args.params = ((args.depth, args.width, int_cycles, ext_cycles),)
  78. else:
  79. params = list(DEFAULT_PARAMS)
  80. args.params = []
  81. for param in params:
  82. if len(param) == 5:
  83. args.params.append(param)
  84. elif len(param) == 4:
  85. for model in args.model_types:
  86. args.params.append((model, *param))
  87. else:
  88. raise ValueError(f'invalid number of params ({param})')
  89. if args.include_engines:
  90. args.include_engines = args.include_engines.split(',')
  91. # TODO if an engine has more than one flavor, serialize here (e.g., xdevs -> all the xdevs stuff)
  92. else:
  93. args.include_engines = []
  94. serialize_simengines(args.include_engines, '', COMMANDS)
  95. if args.exclude_engines:
  96. args.include_engines = [x for x in args.include_engines if x not in args.exclude_engines.split(",")]
  97. args.commands = {}
  98. args.regex = {}
  99. for eng in args.include_engines:
  100. cmd = COMMANDS
  101. regex = CUSTOM_RE
  102. for s in eng.split('-'):
  103. cmd = cmd.get(s, dict())
  104. if not isinstance(regex, str):
  105. regex = regex.get(s, DEFAULT_RE)
  106. if not cmd:
  107. raise RuntimeError(f'unknown simulation engine {eng}')
  108. if isinstance(cmd, dict):
  109. # TODO instead of this, serialize all the flavors of the engine
  110. raise RuntimeError(f'There are more than one flavor for the engine {eng}')
  111. else:
  112. args.commands[eng] = cmd
  113. args.regex[eng] = regex
  114. if not args.num_rep:
  115. args.num_rep = DEFAULT_NUM_REPS
  116. if not args.out_file:
  117. args.out_file = "devstone_%de_%dp_%d.csv" % (len(args.include_engines), len(args.params), int(time.time()))
  118. return args
  119. def execute_cmd(cmd, regex, csv_writer):
  120. # Execute simulation
  121. try:
  122. result = subprocess.run(cmd.split(), stdout=subprocess.PIPE)
  123. except Exception as e:
  124. print(f"{engine}: Error executing simulation. ({str(e)})")
  125. return
  126. # Read data from output
  127. res_out = result.stdout.decode('UTF-8').strip()
  128. if not res_out:
  129. res_out = str(result.stderr).strip()
  130. res_out.replace("\n", " ")
  131. found = re.search(regex, res_out, flags=re.IGNORECASE | re.DOTALL)
  132. if not found:
  133. print(f"{engine}: Simulation execution times could not be extracted.")
  134. print(result.stdout)
  135. return
  136. groups = found.groups()
  137. if len(groups) == 3:
  138. model_time, engine_time, sim_time = tuple(map(float, found.groups()))
  139. elif len(groups) == 6:
  140. times = list(map(float, (groups[0], groups[2], groups[4])))
  141. units = (groups[1], groups[3], groups[5])
  142. for i in range(3):
  143. if units[i]:
  144. unit = units[i].strip()
  145. if unit.startswith('s'):
  146. pass
  147. elif unit.startswith('ms'):
  148. times[i] *= 1e-3
  149. elif unit.startswith('us') or unit.startswith('µs'):
  150. times[i] *= 1e-6
  151. elif unit.startswith('ns'):
  152. times[i] *= 1e-9
  153. else:
  154. raise Exception(f"unknown time units: {unit}")
  155. times = tuple(times)
  156. model_time, engine_time, sim_time = times
  157. total_time = sum((model_time, engine_time, sim_time))
  158. # Write results into output file
  159. row = (
  160. engine, i_exec, model_type, depth, width, int_cycles, ext_cycles, model_time, engine_time, sim_time, total_time)
  161. csv_writer.writerow(row)
  162. if __name__ == "__main__":
  163. sys.setrecursionlimit(10000)
  164. args = parse_args()
  165. if not args.include_engines:
  166. raise RuntimeError("No engines were selected.")
  167. with open(args.out_file, "w") as csv_file:
  168. csv_writer = csv.writer(csv_file, delimiter=';')
  169. csv_writer.writerow(("engine", "iter", "model", "depth", "width", "int_delay", "ext_delay", "model_time", "runner_time", "sim_time", "total_time"))
  170. for engine, engine_cmd in args.commands.items():
  171. engine_regex = args.regex[engine]
  172. for params in args.params:
  173. model_type, depth, width, int_cycles, ext_cycles = params
  174. engine_cmd_f = engine_cmd.format(model_type=model_type, depth=depth, width=width,
  175. int_cycles=int_cycles, ext_cycles=ext_cycles)
  176. if not engine_cmd_f:
  177. continue
  178. for i_exec in range(args.num_rep):
  179. print(f'({i_exec}) {engine_cmd_f}')
  180. execute_cmd(engine_cmd_f, engine_regex, csv_writer)