Ahmed Karaman | 01afa75 | 2020-07-09 07:20:55 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | # Print the percentage of instructions spent in each phase of QEMU |
| 4 | # execution. |
| 5 | # |
| 6 | # Syntax: |
| 7 | # dissect.py [-h] -- <qemu executable> [<qemu executable options>] \ |
| 8 | # <target executable> [<target executable options>] |
| 9 | # |
| 10 | # [-h] - Print the script arguments help message. |
| 11 | # |
| 12 | # Example of usage: |
| 13 | # dissect.py -- qemu-arm coulomb_double-arm |
| 14 | # |
| 15 | # This file is a part of the project "TCG Continuous Benchmarking". |
| 16 | # |
| 17 | # Copyright (C) 2020 Ahmed Karaman <ahmedkhaledkaraman@gmail.com> |
| 18 | # Copyright (C) 2020 Aleksandar Markovic <aleksandar.qemu.devel@gmail.com> |
| 19 | # |
| 20 | # This program is free software: you can redistribute it and/or modify |
| 21 | # it under the terms of the GNU General Public License as published by |
| 22 | # the Free Software Foundation, either version 2 of the License, or |
| 23 | # (at your option) any later version. |
| 24 | # |
| 25 | # This program is distributed in the hope that it will be useful, |
| 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 28 | # GNU General Public License for more details. |
| 29 | # |
| 30 | # You should have received a copy of the GNU General Public License |
| 31 | # along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 32 | |
| 33 | import argparse |
| 34 | import os |
| 35 | import subprocess |
| 36 | import sys |
| 37 | import tempfile |
| 38 | |
| 39 | |
| 40 | def get_JIT_line(callgrind_data): |
| 41 | """ |
| 42 | Search for the first instance of the JIT call in |
| 43 | the callgrind_annotate output when ran using --tree=caller |
| 44 | This is equivalent to the self number of instructions of JIT. |
| 45 | |
| 46 | Parameters: |
| 47 | callgrind_data (list): callgrind_annotate output |
| 48 | |
| 49 | Returns: |
| 50 | (int): Line number |
| 51 | """ |
| 52 | line = -1 |
| 53 | for i in range(len(callgrind_data)): |
| 54 | if callgrind_data[i].strip('\n') and \ |
| 55 | callgrind_data[i].split()[-1] == "[???]": |
| 56 | line = i |
| 57 | break |
| 58 | if line == -1: |
| 59 | sys.exit("Couldn't locate the JIT call ... Exiting.") |
| 60 | return line |
| 61 | |
| 62 | |
| 63 | def main(): |
| 64 | # Parse the command line arguments |
| 65 | parser = argparse.ArgumentParser( |
| 66 | usage='dissect.py [-h] -- ' |
| 67 | '<qemu executable> [<qemu executable options>] ' |
| 68 | '<target executable> [<target executable options>]') |
| 69 | |
| 70 | parser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS) |
| 71 | |
| 72 | args = parser.parse_args() |
| 73 | |
| 74 | # Extract the needed variables from the args |
| 75 | command = args.command |
| 76 | |
| 77 | # Insure that valgrind is installed |
| 78 | check_valgrind = subprocess.run( |
| 79 | ["which", "valgrind"], stdout=subprocess.DEVNULL) |
| 80 | if check_valgrind.returncode: |
| 81 | sys.exit("Please install valgrind before running the script.") |
| 82 | |
| 83 | # Save all intermediate files in a temporary directory |
| 84 | with tempfile.TemporaryDirectory() as tmpdirname: |
| 85 | # callgrind output file path |
| 86 | data_path = os.path.join(tmpdirname, "callgrind.data") |
| 87 | # callgrind_annotate output file path |
| 88 | annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out") |
| 89 | |
| 90 | # Run callgrind |
| 91 | callgrind = subprocess.run((["valgrind", |
| 92 | "--tool=callgrind", |
| 93 | "--callgrind-out-file=" + data_path] |
| 94 | + command), |
| 95 | stdout=subprocess.DEVNULL, |
| 96 | stderr=subprocess.PIPE) |
| 97 | if callgrind.returncode: |
| 98 | sys.exit(callgrind.stderr.decode("utf-8")) |
| 99 | |
| 100 | # Save callgrind_annotate output |
| 101 | with open(annotate_out_path, "w") as output: |
| 102 | callgrind_annotate = subprocess.run( |
| 103 | ["callgrind_annotate", data_path, "--tree=caller"], |
| 104 | stdout=output, |
| 105 | stderr=subprocess.PIPE) |
| 106 | if callgrind_annotate.returncode: |
| 107 | sys.exit(callgrind_annotate.stderr.decode("utf-8")) |
| 108 | |
| 109 | # Read the callgrind_annotate output to callgrind_data[] |
| 110 | callgrind_data = [] |
| 111 | with open(annotate_out_path, 'r') as data: |
| 112 | callgrind_data = data.readlines() |
| 113 | |
| 114 | # Line number with the total number of instructions |
| 115 | total_instructions_line_number = 20 |
| 116 | # Get the total number of instructions |
| 117 | total_instructions_line_data = \ |
| 118 | callgrind_data[total_instructions_line_number] |
| 119 | total_instructions = total_instructions_line_data.split()[0] |
| 120 | total_instructions = int(total_instructions.replace(',', '')) |
| 121 | |
| 122 | # Line number with the JIT self number of instructions |
| 123 | JIT_self_instructions_line_number = get_JIT_line(callgrind_data) |
| 124 | # Get the JIT self number of instructions |
| 125 | JIT_self_instructions_line_data = \ |
| 126 | callgrind_data[JIT_self_instructions_line_number] |
| 127 | JIT_self_instructions = JIT_self_instructions_line_data.split()[0] |
| 128 | JIT_self_instructions = int(JIT_self_instructions.replace(',', '')) |
| 129 | |
| 130 | # Line number with the JIT self + inclusive number of instructions |
| 131 | # It's the line above the first JIT call when running with --tree=caller |
| 132 | JIT_total_instructions_line_number = JIT_self_instructions_line_number-1 |
| 133 | # Get the JIT self + inclusive number of instructions |
| 134 | JIT_total_instructions_line_data = \ |
| 135 | callgrind_data[JIT_total_instructions_line_number] |
| 136 | JIT_total_instructions = JIT_total_instructions_line_data.split()[0] |
| 137 | JIT_total_instructions = int(JIT_total_instructions.replace(',', '')) |
| 138 | |
| 139 | # Calculate number of instructions in helpers and code generation |
| 140 | helpers_instructions = JIT_total_instructions-JIT_self_instructions |
| 141 | code_generation_instructions = total_instructions-JIT_total_instructions |
| 142 | |
| 143 | # Print results (Insert commas in large numbers) |
| 144 | # Print total number of instructions |
| 145 | print('{:<20}{:>20}\n'. |
| 146 | format("Total Instructions:", |
| 147 | format(total_instructions, ','))) |
| 148 | # Print code generation instructions and percentage |
| 149 | print('{:<20}{:>20}\t{:>6.3f}%'. |
| 150 | format("Code Generation:", |
| 151 | format(code_generation_instructions, ","), |
| 152 | (code_generation_instructions / total_instructions) * 100)) |
| 153 | # Print JIT instructions and percentage |
| 154 | print('{:<20}{:>20}\t{:>6.3f}%'. |
| 155 | format("JIT Execution:", |
| 156 | format(JIT_self_instructions, ","), |
| 157 | (JIT_self_instructions / total_instructions) * 100)) |
| 158 | # Print helpers instructions and percentage |
| 159 | print('{:<20}{:>20}\t{:>6.3f}%'. |
| 160 | format("Helpers:", |
| 161 | format(helpers_instructions, ","), |
| 162 | (helpers_instructions/total_instructions)*100)) |
| 163 | |
| 164 | |
| 165 | if __name__ == "__main__": |
| 166 | main() |