167 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			167 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | #!/usr/bin/env python3 | ||
|  | 
 | ||
|  | #  Print the percentage of instructions spent in each phase of QEMU | ||
|  | #  execution. | ||
|  | # | ||
|  | #  Syntax: | ||
|  | #  dissect.py [-h] -- <qemu executable> [<qemu executable options>] \ | ||
|  | #                   <target executable> [<target executable options>] | ||
|  | # | ||
|  | #  [-h] - Print the script arguments help message. | ||
|  | # | ||
|  | #  Example of usage: | ||
|  | #  dissect.py -- qemu-arm coulomb_double-arm | ||
|  | # | ||
|  | #  This file is a part of the project "TCG Continuous Benchmarking". | ||
|  | # | ||
|  | #  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com> | ||
|  | #  Copyright (C) 2020  Aleksandar Markovic <aleksandar.qemu.devel@gmail.com> | ||
|  | # | ||
|  | #  This program is free software: you can redistribute it and/or modify | ||
|  | #  it under the terms of the GNU General Public License as published by | ||
|  | #  the Free Software Foundation, either version 2 of the License, or | ||
|  | #  (at your option) any later version. | ||
|  | # | ||
|  | #  This program is distributed in the hope that it will be useful, | ||
|  | #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
|  | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
|  | #  GNU General Public License for more details. | ||
|  | # | ||
|  | #  You should have received a copy of the GNU General Public License | ||
|  | #  along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|  | 
 | ||
|  | import argparse | ||
|  | import os | ||
|  | import subprocess | ||
|  | import sys | ||
|  | import tempfile | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_JIT_line(callgrind_data): | ||
|  |     """
 | ||
|  |     Search for the first instance of the JIT call in | ||
|  |     the callgrind_annotate output when ran using --tree=caller | ||
|  |     This is equivalent to the self number of instructions of JIT. | ||
|  | 
 | ||
|  |     Parameters: | ||
|  |     callgrind_data (list): callgrind_annotate output | ||
|  | 
 | ||
|  |     Returns: | ||
|  |     (int): Line number | ||
|  |     """
 | ||
|  |     line = -1 | ||
|  |     for i in range(len(callgrind_data)): | ||
|  |         if callgrind_data[i].strip('\n') and \ | ||
|  |                 callgrind_data[i].split()[-1] == "[???]": | ||
|  |             line = i | ||
|  |             break | ||
|  |     if line == -1: | ||
|  |         sys.exit("Couldn't locate the JIT call ... Exiting.") | ||
|  |     return line | ||
|  | 
 | ||
|  | 
 | ||
|  | def main(): | ||
|  |     # Parse the command line arguments | ||
|  |     parser = argparse.ArgumentParser( | ||
|  |         usage='dissect.py [-h] -- ' | ||
|  |         '<qemu executable> [<qemu executable options>] ' | ||
|  |         '<target executable> [<target executable options>]') | ||
|  | 
 | ||
|  |     parser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS) | ||
|  | 
 | ||
|  |     args = parser.parse_args() | ||
|  | 
 | ||
|  |     # Extract the needed variables from the args | ||
|  |     command = args.command | ||
|  | 
 | ||
|  |     # Insure that valgrind is installed | ||
|  |     check_valgrind = subprocess.run( | ||
|  |         ["which", "valgrind"], stdout=subprocess.DEVNULL) | ||
|  |     if check_valgrind.returncode: | ||
|  |         sys.exit("Please install valgrind before running the script.") | ||
|  | 
 | ||
|  |     # Save all intermediate files in a temporary directory | ||
|  |     with tempfile.TemporaryDirectory() as tmpdirname: | ||
|  |         # callgrind output file path | ||
|  |         data_path = os.path.join(tmpdirname, "callgrind.data") | ||
|  |         # callgrind_annotate output file path | ||
|  |         annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out") | ||
|  | 
 | ||
|  |         # Run callgrind | ||
|  |         callgrind = subprocess.run((["valgrind", | ||
|  |                                      "--tool=callgrind", | ||
|  |                                      "--callgrind-out-file=" + data_path] | ||
|  |                                     + command), | ||
|  |                                    stdout=subprocess.DEVNULL, | ||
|  |                                    stderr=subprocess.PIPE) | ||
|  |         if callgrind.returncode: | ||
|  |             sys.exit(callgrind.stderr.decode("utf-8")) | ||
|  | 
 | ||
|  |         # Save callgrind_annotate output | ||
|  |         with open(annotate_out_path, "w") as output: | ||
|  |             callgrind_annotate = subprocess.run( | ||
|  |                 ["callgrind_annotate", data_path, "--tree=caller"], | ||
|  |                 stdout=output, | ||
|  |                 stderr=subprocess.PIPE) | ||
|  |             if callgrind_annotate.returncode: | ||
|  |                 sys.exit(callgrind_annotate.stderr.decode("utf-8")) | ||
|  | 
 | ||
|  |         # Read the callgrind_annotate output to callgrind_data[] | ||
|  |         callgrind_data = [] | ||
|  |         with open(annotate_out_path, 'r') as data: | ||
|  |             callgrind_data = data.readlines() | ||
|  | 
 | ||
|  |         # Line number with the total number of instructions | ||
|  |         total_instructions_line_number = 20 | ||
|  |         # Get the total number of instructions | ||
|  |         total_instructions_line_data = \ | ||
|  |             callgrind_data[total_instructions_line_number] | ||
|  |         total_instructions = total_instructions_line_data.split()[0] | ||
|  |         total_instructions = int(total_instructions.replace(',', '')) | ||
|  | 
 | ||
|  |         # Line number with the JIT self number of instructions | ||
|  |         JIT_self_instructions_line_number = get_JIT_line(callgrind_data) | ||
|  |         # Get the JIT self number of instructions | ||
|  |         JIT_self_instructions_line_data = \ | ||
|  |             callgrind_data[JIT_self_instructions_line_number] | ||
|  |         JIT_self_instructions = JIT_self_instructions_line_data.split()[0] | ||
|  |         JIT_self_instructions = int(JIT_self_instructions.replace(',', '')) | ||
|  | 
 | ||
|  |         # Line number with the JIT self + inclusive number of instructions | ||
|  |         # It's the line above the first JIT call when running with --tree=caller | ||
|  |         JIT_total_instructions_line_number = JIT_self_instructions_line_number-1 | ||
|  |         # Get the JIT self + inclusive number of instructions | ||
|  |         JIT_total_instructions_line_data = \ | ||
|  |             callgrind_data[JIT_total_instructions_line_number] | ||
|  |         JIT_total_instructions = JIT_total_instructions_line_data.split()[0] | ||
|  |         JIT_total_instructions = int(JIT_total_instructions.replace(',', '')) | ||
|  | 
 | ||
|  |         # Calculate number of instructions in helpers and code generation | ||
|  |         helpers_instructions = JIT_total_instructions-JIT_self_instructions | ||
|  |         code_generation_instructions = total_instructions-JIT_total_instructions | ||
|  | 
 | ||
|  |         # Print results (Insert commas in large numbers) | ||
|  |         # Print total number of instructions | ||
|  |         print('{:<20}{:>20}\n'. | ||
|  |               format("Total Instructions:", | ||
|  |                      format(total_instructions, ','))) | ||
|  |         # Print code generation instructions and percentage | ||
|  |         print('{:<20}{:>20}\t{:>6.3f}%'. | ||
|  |               format("Code Generation:", | ||
|  |                      format(code_generation_instructions, ","), | ||
|  |                      (code_generation_instructions / total_instructions) * 100)) | ||
|  |         # Print JIT instructions and percentage | ||
|  |         print('{:<20}{:>20}\t{:>6.3f}%'. | ||
|  |               format("JIT Execution:", | ||
|  |                      format(JIT_self_instructions, ","), | ||
|  |                      (JIT_self_instructions / total_instructions) * 100)) | ||
|  |         # Print helpers instructions and percentage | ||
|  |         print('{:<20}{:>20}\t{:>6.3f}%'. | ||
|  |               format("Helpers:", | ||
|  |                      format(helpers_instructions, ","), | ||
|  |                      (helpers_instructions/total_instructions)*100)) | ||
|  | 
 | ||
|  | 
 | ||
|  | if __name__ == "__main__": | ||
|  |     main() |