]>
Commit | Line | Data |
---|---|---|
01afa757 AK |
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() |