]>
Commit | Line | Data |
---|---|---|
5c362ccf AK |
1 | #!/usr/bin/env python3 |
2 | ||
3 | # Print the top N most executed functions in QEMU using callgrind. | |
4 | # Syntax: | |
5 | # topN_callgrind.py [-h] [-n] <number of displayed top functions> -- \ | |
6 | # <qemu executable> [<qemu executable options>] \ | |
d30b5bc9 | 7 | # <target executable> [<target executable options>] |
5c362ccf AK |
8 | # |
9 | # [-h] - Print the script arguments help message. | |
10 | # [-n] - Specify the number of top functions to print. | |
11 | # - If this flag is not specified, the tool defaults to 25. | |
12 | # | |
13 | # Example of usage: | |
14 | # topN_callgrind.py -n 20 -- qemu-arm coulomb_double-arm | |
15 | # | |
16 | # This file is a part of the project "TCG Continuous Benchmarking". | |
17 | # | |
18 | # Copyright (C) 2020 Ahmed Karaman <ahmedkhaledkaraman@gmail.com> | |
19 | # Copyright (C) 2020 Aleksandar Markovic <aleksandar.qemu.devel@gmail.com> | |
20 | # | |
21 | # This program is free software: you can redistribute it and/or modify | |
22 | # it under the terms of the GNU General Public License as published by | |
23 | # the Free Software Foundation, either version 2 of the License, or | |
24 | # (at your option) any later version. | |
25 | # | |
26 | # This program is distributed in the hope that it will be useful, | |
27 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
28 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
29 | # GNU General Public License for more details. | |
30 | # | |
31 | # You should have received a copy of the GNU General Public License | |
32 | # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
33 | ||
34 | import argparse | |
35 | import os | |
36 | import subprocess | |
37 | import sys | |
38 | ||
39 | ||
40 | # Parse the command line arguments | |
41 | parser = argparse.ArgumentParser( | |
42 | usage='topN_callgrind.py [-h] [-n] <number of displayed top functions> -- ' | |
43 | '<qemu executable> [<qemu executable options>] ' | |
44 | '<target executable> [<target executable options>]') | |
45 | ||
46 | parser.add_argument('-n', dest='top', type=int, default=25, | |
47 | help='Specify the number of top functions to print.') | |
48 | ||
49 | parser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS) | |
50 | ||
51 | args = parser.parse_args() | |
52 | ||
53 | # Extract the needed variables from the args | |
54 | command = args.command | |
55 | top = args.top | |
56 | ||
57 | # Insure that valgrind is installed | |
58 | check_valgrind_presence = subprocess.run(["which", "valgrind"], | |
59 | stdout=subprocess.DEVNULL) | |
60 | if check_valgrind_presence.returncode: | |
61 | sys.exit("Please install valgrind before running the script!") | |
62 | ||
63 | # Run callgrind | |
64 | callgrind = subprocess.run(( | |
65 | ["valgrind", "--tool=callgrind", "--callgrind-out-file=/tmp/callgrind.data"] | |
66 | + command), | |
67 | stdout=subprocess.DEVNULL, | |
68 | stderr=subprocess.PIPE) | |
69 | if callgrind.returncode: | |
70 | sys.exit(callgrind.stderr.decode("utf-8")) | |
71 | ||
72 | # Save callgrind_annotate output to /tmp/callgrind_annotate.out | |
73 | with open("/tmp/callgrind_annotate.out", "w") as output: | |
74 | callgrind_annotate = subprocess.run(["callgrind_annotate", | |
75 | "/tmp/callgrind.data"], | |
76 | stdout=output, | |
77 | stderr=subprocess.PIPE) | |
78 | if callgrind_annotate.returncode: | |
79 | os.unlink('/tmp/callgrind.data') | |
80 | output.close() | |
81 | os.unlink('/tmp/callgrind_annotate.out') | |
82 | sys.exit(callgrind_annotate.stderr.decode("utf-8")) | |
83 | ||
84 | # Read the callgrind_annotate output to callgrind_data[] | |
85 | callgrind_data = [] | |
86 | with open('/tmp/callgrind_annotate.out', 'r') as data: | |
87 | callgrind_data = data.readlines() | |
88 | ||
89 | # Line number with the total number of instructions | |
90 | total_instructions_line_number = 20 | |
91 | ||
92 | # Get the total number of instructions | |
93 | total_instructions_line_data = callgrind_data[total_instructions_line_number] | |
94 | total_number_of_instructions = total_instructions_line_data.split(' ')[0] | |
95 | total_number_of_instructions = int( | |
96 | total_number_of_instructions.replace(',', '')) | |
97 | ||
98 | # Line number with the top function | |
99 | first_func_line = 25 | |
100 | ||
101 | # Number of functions recorded by callgrind, last two lines are always empty | |
102 | number_of_functions = len(callgrind_data) - first_func_line - 2 | |
103 | ||
104 | # Limit the number of top functions to "top" | |
105 | number_of_top_functions = (top if number_of_functions > | |
106 | top else number_of_functions) | |
107 | ||
108 | # Store the data of the top functions in top_functions[] | |
109 | top_functions = callgrind_data[first_func_line: | |
110 | first_func_line + number_of_top_functions] | |
111 | ||
112 | # Print table header | |
113 | print('{:>4} {:>10} {:<30} {}\n{} {} {} {}'.format('No.', | |
114 | 'Percentage', | |
115 | 'Function Name', | |
116 | 'Source File', | |
117 | '-' * 4, | |
118 | '-' * 10, | |
119 | '-' * 30, | |
120 | '-' * 30, | |
121 | )) | |
122 | ||
123 | # Print top N functions | |
124 | for (index, function) in enumerate(top_functions, start=1): | |
125 | function_data = function.split() | |
126 | # Calculate function percentage | |
127 | function_instructions = float(function_data[0].replace(',', '')) | |
128 | function_percentage = (function_instructions / | |
129 | total_number_of_instructions)*100 | |
130 | # Get function name and source files path | |
131 | function_source_file, function_name = function_data[1].split(':') | |
132 | # Print extracted data | |
133 | print('{:>4} {:>9.3f}% {:<30} {}'.format(index, | |
134 | round(function_percentage, 3), | |
135 | function_name, | |
136 | function_source_file)) | |
137 | ||
138 | # Remove intermediate files | |
139 | os.unlink('/tmp/callgrind.data') | |
140 | os.unlink('/tmp/callgrind_annotate.out') |