#!/usr/bin/env python3 # Print the percentage of instructions spent in each phase of QEMU # execution. # # Syntax: # dissect.py [-h] -- [] \ # [] # # [-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 # Copyright (C) 2020 Aleksandar Markovic # # 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 . 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] -- ' ' [] ' ' []') 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()