utils.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import unittest
  2. import sys
  3. import os
  4. import sys
  5. import time
  6. import json
  7. import urllib
  8. import urllib2
  9. import subprocess
  10. import signal
  11. import random
  12. import operator
  13. sys.path.append("interface/HUTN")
  14. sys.path.append("scripts")
  15. from hutn_compiler.compiler import main as do_compile
  16. from check_objects import to_recompile
  17. USERNAME = "test_task"
  18. PARALLEL_PUSH = True
  19. BOOTSTRAP_FOLDER_NAME = "bootstrap"
  20. CURRENT_FOLDER_NAME = "performance"
  21. PORTS = set()
  22. OPTIMIZATION_LEVEL_LEGACY_INTERPRETER = "legacy-interpreter"
  23. OPTIMIZATION_LEVEL_INTERPRETER = "interpreter"
  24. OPTIMIZATION_LEVEL_BASELINE_JIT = "baseline-jit"
  25. OPTIMIZATION_LEVEL_BASELINE_JIT_NO_THUNKS = "baseline-jit,no-thunks"
  26. OPTIMIZATION_LEVEL_FAST_JIT = "fast-jit"
  27. OPTIMIZATION_LEVEL_FAST_JIT_NO_NOPS = "fast-jit,no-insert-nops"
  28. OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_LARGE_FUNCTIONS = "adaptive-jit-favor-large-functions"
  29. OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_SMALL_FUNCTIONS = "adaptive-jit-favor-small-functions"
  30. OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_LOOPS = "adaptive-jit-favor-loops"
  31. ALL_OPTIMIZATION_LEVELS = [
  32. OPTIMIZATION_LEVEL_LEGACY_INTERPRETER,
  33. OPTIMIZATION_LEVEL_INTERPRETER,
  34. OPTIMIZATION_LEVEL_BASELINE_JIT,
  35. OPTIMIZATION_LEVEL_BASELINE_JIT_NO_THUNKS,
  36. OPTIMIZATION_LEVEL_FAST_JIT,
  37. OPTIMIZATION_LEVEL_FAST_JIT_NO_NOPS,
  38. OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_LARGE_FUNCTIONS,
  39. OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_SMALL_FUNCTIONS,
  40. OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_LOOPS
  41. ]
  42. class ModelverseTerminated(Exception):
  43. """An exception that tells the task that the Modelverse has terminated."""
  44. pass
  45. def get_code_folder_name():
  46. """Gets the name of the code folder."""
  47. return '%s/code' % CURRENT_FOLDER_NAME
  48. def get_free_port():
  49. """Gets a unique new port."""
  50. while 1:
  51. port = random.randint(10000, 20000)
  52. # Check if this port is in the set of ports.
  53. if port not in PORTS:
  54. # We have found a unique port. Add it to the set and return.
  55. PORTS.add(port)
  56. return port
  57. def execute(scriptname, parameters=None, wait=False):
  58. """Runs a script."""
  59. if os.name not in ["nt", "posix"]:
  60. # Stop now, as we would have no clue on how to kill its subtree
  61. raise Exception("Unknown OS version: " + str(os.name))
  62. command = [sys.executable, "scripts/%s.py" % scriptname] + (
  63. [] if parameters is None else parameters)
  64. if wait:
  65. return subprocess.call(command, shell=False)
  66. else:
  67. return subprocess.Popen(command, shell=False)
  68. def kill(process):
  69. """Kills the given process."""
  70. if os.name == "nt":
  71. subprocess.call(["taskkill", "/F", "/T", "/PID", "%i" % process.pid])
  72. elif os.name == "posix":
  73. subprocess.call(["pkill", "-P", "%i" % process.pid])
  74. def set_input_data(address, data):
  75. """Sets the Modelverse program's input data."""
  76. if data is not None:
  77. urllib2.urlopen(
  78. urllib2.Request(
  79. address,
  80. urllib.urlencode(
  81. {"op": "set_input", "data": json.dumps(data), "taskname": USERNAME})),
  82. timeout=10).read()
  83. else:
  84. return []
  85. def compile_file(address, mod_filename, filename, mode, proc):
  86. """Compiles the given file."""
  87. # Load in the file required
  88. try:
  89. timeout_val = 240
  90. taskname = str(random.random())
  91. while 1:
  92. proc2 = execute(
  93. "compile", [address, mod_filename, taskname, filename, mode], wait=False)
  94. if proc.returncode is not None:
  95. # Modelverse has already terminated, which isn't a good sign!
  96. raise Exception("Modelverse died!")
  97. while proc2.returncode is None:
  98. time.sleep(0.01)
  99. proc2.poll()
  100. timeout_val -= 0.01
  101. if timeout_val < 0:
  102. kill(proc2)
  103. print("Compilation timeout expired!")
  104. return False
  105. if proc2.returncode != 2:
  106. break
  107. # Make sure everything stopped correctly
  108. assert proc2.returncode == 0
  109. if proc2.returncode != 0:
  110. return False
  111. except:
  112. raise
  113. finally:
  114. try:
  115. kill(proc2)
  116. except UnboundLocalError:
  117. pass
  118. def run_file(files, parameters, mode, handle_output, optimization_level=None):
  119. """Compiles the given sequence of files, feeds them the given input in the given mode,
  120. and handles their output."""
  121. # Resolve file
  122. import os.path
  123. time.sleep(0.01)
  124. port = get_free_port()
  125. address = "http://127.0.0.1:%i" % port
  126. try:
  127. # Run Modelverse server
  128. modelverse_args = [str(port)]
  129. if optimization_level is not None:
  130. modelverse_args.append('--kernel=%s' % optimization_level)
  131. proc = execute("run_local_modelverse", modelverse_args, wait=False)
  132. threads = []
  133. mod_files = []
  134. for filename in files:
  135. if os.path.isfile("%s/%s" % (get_code_folder_name(), filename)):
  136. mod_filename = "%s/%s" % (get_code_folder_name(), filename)
  137. elif os.path.isfile("%s/%s" % (BOOTSTRAP_FOLDER_NAME, filename)):
  138. mod_filename = "%s/%s" % (BOOTSTRAP_FOLDER_NAME, filename)
  139. else:
  140. raise Exception("File not found: %s" % filename)
  141. mod_files.append(mod_filename)
  142. to_compile = to_recompile(address, mod_files)
  143. for mod_filename in to_compile:
  144. if PARALLEL_PUSH:
  145. import threading
  146. threads.append(
  147. threading.Thread(
  148. target=compile_file,
  149. args=[address, mod_filename, mod_filename, mode, proc]))
  150. threads[-1].start()
  151. else:
  152. compile_file(address, mod_filename, mod_filename, mode, proc)
  153. if PARALLEL_PUSH:
  154. for t in threads:
  155. t.join()
  156. if mode[-1] == "O":
  157. # Fire up the linker
  158. val = execute("link_and_load", [address, USERNAME] + mod_files, wait=True)
  159. if val != 0:
  160. raise Exception("Linking error")
  161. # Send the request ...
  162. set_input_data(address, parameters)
  163. # ... and wait for replies
  164. while 1:
  165. val = urllib2.urlopen(
  166. urllib2.Request(
  167. address,
  168. urllib.urlencode({"op": "get_output", "taskname": USERNAME})),
  169. timeout=240).read()
  170. val = json.loads(val)
  171. if proc.returncode is not None:
  172. # Modelverse has terminated. This may or may not be what we want.
  173. raise ModelverseTerminated()
  174. if not handle_output(val):
  175. return
  176. # All passed!
  177. return
  178. except:
  179. raise
  180. finally:
  181. try:
  182. kill(proc)
  183. except UnboundLocalError:
  184. pass
  185. def run_file_to_completion(files, parameters, mode):
  186. """Compiles the given sequence of files, feeds them the given input in the given mode,
  187. and then collects and returns output."""
  188. results = []
  189. def handle_output(output):
  190. """Appends the given output to the list of results."""
  191. results.append(output)
  192. return True
  193. try:
  194. run_file(files, parameters, mode, handle_output)
  195. except ModelverseTerminated:
  196. return results
  197. def run_file_fixed_output_count(files, parameters, mode, output_count, optimization_level=None):
  198. """Compiles the given sequence of files, feeds them the given input in the given mode,
  199. and then collects and returns a fixed number of outputs."""
  200. results = []
  201. def handle_output(output):
  202. """Appends the given output to the list of results."""
  203. if len(results) < output_count:
  204. results.append(output)
  205. return True
  206. else:
  207. return False
  208. run_file(files, parameters, mode, handle_output, optimization_level)
  209. return results
  210. def run_file_single_output(files, parameters, mode, optimization_level=None):
  211. """Compiles the given sequence of files, feeds them the given input in the given mode,
  212. and then collects and returns a single output."""
  213. return run_file_fixed_output_count(files, parameters, mode, 1, optimization_level)[0]
  214. def run_perf_test(files, parameters, optimization_level, n_iterations=1):
  215. """Compiles the given sequence of files, feeds them the given input in the given mode,
  216. and then collects their output. This process is repeated n_iterations times. The
  217. return value is the average of all outputs."""
  218. result = 0.0
  219. for _ in xrange(n_iterations):
  220. result += float(
  221. run_file_single_output(
  222. files, parameters + [0], 'CO',
  223. optimization_level)) / float(n_iterations)
  224. return result
  225. def format_output(output):
  226. """Formats the output of `run_file_to_completion` as a string."""
  227. return '\n'.join(output)
  228. def define_perf_test(target_class, test_function, optimization_level):
  229. """Defines a performance test in the given class. The performance test calls the given function
  230. at the given optimization level."""
  231. setattr(
  232. target_class,
  233. 'test_%s' % optimization_level.replace('-', '_').lower(),
  234. lambda self: test_function(self, optimization_level))
  235. def define_perf_tests(target_class, test_function):
  236. """Defines performance tests in the given class. Each test calls the given function."""
  237. for optimization_level in ALL_OPTIMIZATION_LEVELS:
  238. define_perf_test(target_class, test_function, optimization_level)
  239. DEFAULT_PERF_FILE_NAME = 'perf_data.txt'
  240. def write_perf_to_file(test_name, optimization_level, result, file_name=DEFAULT_PERF_FILE_NAME):
  241. """Writes performance data to a file."""
  242. with open(file_name, "a") as perf_file:
  243. perf_file.write('%s:%s:%f\n' % (test_name, optimization_level, result))
  244. def parse_perf_data(file_name):
  245. """Parses the performance data in the given file."""
  246. results = {}
  247. with open(file_name, 'r') as perf_file:
  248. for line in perf_file.readlines():
  249. test_name, optimization_level, result = line.strip().split(':')
  250. if optimization_level not in results:
  251. results[optimization_level] = []
  252. results[optimization_level].append((test_name, result))
  253. return sorted(results.items(), key=operator.itemgetter(1))