util.py 5.1 KB

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