Skip to content

Commit 5c362cc

Browse files
ahmedkrmnAMarkovic
authored andcommitted
scripts/performance: Add topN_callgrind.py script
Python script that prints the top N most executed functions in QEMU using callgrind. Syntax: topN_callgrind.py [-h] [-n] <number of displayed top functions> -- \ <qemu executable> [<qemu executable options>] \ <target executable> [<target execurable options>] [-h] - Print the script arguments help message. [-n] - Specify the number of top functions to print. - If this flag is not specified, the tool defaults to 25. Example of usage: topN_callgrind.py -n 20 -- qemu-arm coulomb_double-arm Example Output: No. Percentage Function Name Source File ---- --------- ------------------ ------------------------------ 1 24.577% 0x00000000082db000 ??? 2 20.467% float64_mul <qemu>/fpu/softfloat.c 3 14.720% float64_sub <qemu>/fpu/softfloat.c 4 13.864% float64_add <qemu>/fpu/softfloat.c 5 4.876% helper_mulsd <qemu>/target/i386/ops_sse.h 6 3.767% helper_subsd <qemu>/target/i386/ops_sse.h 7 3.549% helper_addsd <qemu>/target/i386/ops_sse.h 8 2.185% helper_ucomisd <qemu>/target/i386/ops_sse.h 9 1.667% helper_lookup_tb_ptr <qemu>/include/exec/tb-lookup.h 10 1.662% f64_compare <qemu>/fpu/softfloat.c 11 1.509% helper_lookup_tb_ptr <qemu>/accel/tcg/tcg-runtime.c 12 0.635% helper_lookup_tb_ptr <qemu>/include/exec/exec-all.h 13 0.616% float64_div <qemu>/fpu/softfloat.c 14 0.502% helper_pand_xmm <qemu>/target/i386/ops_sse.h 15 0.502% float64_mul <qemu>/include/fpu/softfloat.h 16 0.476% helper_lookup_tb_ptr <qemu>/target/i386/cpu.h 17 0.437% float64_compare_quiet <qemu>/fpu/softfloat.c 18 0.414% helper_pxor_xmm <qemu>/target/i386/ops_sse.h 19 0.353% round_to_int <qemu>/fpu/softfloat.c 20 0.347% helper_cc_compute_all <qemu>/target/i386/cc_helper.c Signed-off-by: Ahmed Karaman <[email protected]> Signed-off-by: Aleksandar Markovic <[email protected]> Reviewed-by: Aleksandar Markovic <[email protected]> Message-Id: <[email protected]>
1 parent c5a5839 commit 5c362cc

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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>] \
7+
# <target executable> [<target execurable options>]
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 <[email protected]>
19+
# Copyright (C) 2020 Aleksandar Markovic <[email protected]>
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')

0 commit comments

Comments
 (0)