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.

203 lines
6.7 KiB

1 month ago
  1. """util.py - General utilities for running, loading, and processing benchmarks
  2. """
  3. import json
  4. import os
  5. import re
  6. import subprocess
  7. import sys
  8. import tempfile
  9. # Input file type enumeration
  10. IT_Invalid = 0
  11. IT_JSON = 1
  12. IT_Executable = 2
  13. _num_magic_bytes = 2 if sys.platform.startswith('win') else 4
  14. def is_executable_file(filename):
  15. """
  16. Return 'True' if 'filename' names a valid file which is likely
  17. an executable. A file is considered an executable if it starts with the
  18. magic bytes for a EXE, Mach O, or ELF file.
  19. """
  20. if not os.path.isfile(filename):
  21. return False
  22. with open(filename, mode='rb') as f:
  23. magic_bytes = f.read(_num_magic_bytes)
  24. if sys.platform == 'darwin':
  25. return magic_bytes in [
  26. b'\xfe\xed\xfa\xce', # MH_MAGIC
  27. b'\xce\xfa\xed\xfe', # MH_CIGAM
  28. b'\xfe\xed\xfa\xcf', # MH_MAGIC_64
  29. b'\xcf\xfa\xed\xfe', # MH_CIGAM_64
  30. b'\xca\xfe\xba\xbe', # FAT_MAGIC
  31. b'\xbe\xba\xfe\xca' # FAT_CIGAM
  32. ]
  33. elif sys.platform.startswith('win'):
  34. return magic_bytes == b'MZ'
  35. else:
  36. return magic_bytes == b'\x7FELF'
  37. def is_json_file(filename):
  38. """
  39. Returns 'True' if 'filename' names a valid JSON output file.
  40. 'False' otherwise.
  41. """
  42. try:
  43. with open(filename, 'r') as f:
  44. json.load(f)
  45. return True
  46. except BaseException:
  47. pass
  48. return False
  49. def classify_input_file(filename):
  50. """
  51. Return a tuple (type, msg) where 'type' specifies the classified type
  52. of 'filename'. If 'type' is 'IT_Invalid' then 'msg' is a human readable
  53. string representing the error.
  54. """
  55. ftype = IT_Invalid
  56. err_msg = None
  57. if not os.path.exists(filename):
  58. err_msg = "'%s' does not exist" % filename
  59. elif not os.path.isfile(filename):
  60. err_msg = "'%s' does not name a file" % filename
  61. elif is_executable_file(filename):
  62. ftype = IT_Executable
  63. elif is_json_file(filename):
  64. ftype = IT_JSON
  65. else:
  66. err_msg = "'%s' does not name a valid benchmark executable or JSON file" % filename
  67. return ftype, err_msg
  68. def check_input_file(filename):
  69. """
  70. Classify the file named by 'filename' and return the classification.
  71. If the file is classified as 'IT_Invalid' print an error message and exit
  72. the program.
  73. """
  74. ftype, msg = classify_input_file(filename)
  75. if ftype == IT_Invalid:
  76. print("Invalid input file: %s" % msg)
  77. sys.exit(1)
  78. return ftype
  79. def find_benchmark_flag(prefix, benchmark_flags):
  80. """
  81. Search the specified list of flags for a flag matching `<prefix><arg>` and
  82. if it is found return the arg it specifies. If specified more than once the
  83. last value is returned. If the flag is not found None is returned.
  84. """
  85. assert prefix.startswith('--') and prefix.endswith('=')
  86. result = None
  87. for f in benchmark_flags:
  88. if f.startswith(prefix):
  89. result = f[len(prefix):]
  90. return result
  91. def remove_benchmark_flags(prefix, benchmark_flags):
  92. """
  93. Return a new list containing the specified benchmark_flags except those
  94. with the specified prefix.
  95. """
  96. assert prefix.startswith('--') and prefix.endswith('=')
  97. return [f for f in benchmark_flags if not f.startswith(prefix)]
  98. def load_benchmark_results(fname, benchmark_filter):
  99. """
  100. Read benchmark output from a file and return the JSON object.
  101. Apply benchmark_filter, a regular expression, with nearly the same
  102. semantics of the --benchmark_filter argument. May be None.
  103. Note: the Python regular expression engine is used instead of the
  104. one used by the C++ code, which may produce different results
  105. in complex cases.
  106. REQUIRES: 'fname' names a file containing JSON benchmark output.
  107. """
  108. def benchmark_wanted(benchmark):
  109. if benchmark_filter is None:
  110. return True
  111. name = benchmark.get('run_name', None) or benchmark['name']
  112. if re.search(benchmark_filter, name):
  113. return True
  114. return False
  115. with open(fname, 'r') as f:
  116. results = json.load(f)
  117. if 'benchmarks' in results:
  118. results['benchmarks'] = list(filter(benchmark_wanted,
  119. results['benchmarks']))
  120. return results
  121. def sort_benchmark_results(result):
  122. benchmarks = result['benchmarks']
  123. # From inner key to the outer key!
  124. benchmarks = sorted(
  125. benchmarks, key=lambda benchmark: benchmark['repetition_index'] if 'repetition_index' in benchmark else -1)
  126. benchmarks = sorted(
  127. benchmarks, key=lambda benchmark: 1 if 'run_type' in benchmark and benchmark['run_type'] == "aggregate" else 0)
  128. benchmarks = sorted(
  129. benchmarks, key=lambda benchmark: benchmark['per_family_instance_index'] if 'per_family_instance_index' in benchmark else -1)
  130. benchmarks = sorted(
  131. benchmarks, key=lambda benchmark: benchmark['family_index'] if 'family_index' in benchmark else -1)
  132. result['benchmarks'] = benchmarks
  133. return result
  134. def run_benchmark(exe_name, benchmark_flags):
  135. """
  136. Run a benchmark specified by 'exe_name' with the specified
  137. 'benchmark_flags'. The benchmark is run directly as a subprocess to preserve
  138. real time console output.
  139. RETURNS: A JSON object representing the benchmark output
  140. """
  141. output_name = find_benchmark_flag('--benchmark_out=',
  142. benchmark_flags)
  143. is_temp_output = False
  144. if output_name is None:
  145. is_temp_output = True
  146. thandle, output_name = tempfile.mkstemp()
  147. os.close(thandle)
  148. benchmark_flags = list(benchmark_flags) + \
  149. ['--benchmark_out=%s' % output_name]
  150. cmd = [exe_name] + benchmark_flags
  151. print("RUNNING: %s" % ' '.join(cmd))
  152. exitCode = subprocess.call(cmd)
  153. if exitCode != 0:
  154. print('TEST FAILED...')
  155. sys.exit(exitCode)
  156. json_res = load_benchmark_results(output_name, None)
  157. if is_temp_output:
  158. os.unlink(output_name)
  159. return json_res
  160. def run_or_load_benchmark(filename, benchmark_flags):
  161. """
  162. Get the results for a specified benchmark. If 'filename' specifies
  163. an executable benchmark then the results are generated by running the
  164. benchmark. Otherwise 'filename' must name a valid JSON output file,
  165. which is loaded and the result returned.
  166. """
  167. ftype = check_input_file(filename)
  168. if ftype == IT_JSON:
  169. benchmark_filter = find_benchmark_flag('--benchmark_filter=',
  170. benchmark_flags)
  171. return load_benchmark_results(filename, benchmark_filter)
  172. if ftype == IT_Executable:
  173. return run_benchmark(filename, benchmark_flags)
  174. raise ValueError('Unknown file type %s' % ftype)