15c362ccfSAhmed Karaman#!/usr/bin/env python3 25c362ccfSAhmed Karaman 35c362ccfSAhmed Karaman# Print the top N most executed functions in QEMU using callgrind. 45c362ccfSAhmed Karaman# Syntax: 55c362ccfSAhmed Karaman# topN_callgrind.py [-h] [-n] <number of displayed top functions> -- \ 65c362ccfSAhmed Karaman# <qemu executable> [<qemu executable options>] \ 7*d30b5bc9SMichael Tokarev# <target executable> [<target executable options>] 85c362ccfSAhmed Karaman# 95c362ccfSAhmed Karaman# [-h] - Print the script arguments help message. 105c362ccfSAhmed Karaman# [-n] - Specify the number of top functions to print. 115c362ccfSAhmed Karaman# - If this flag is not specified, the tool defaults to 25. 125c362ccfSAhmed Karaman# 135c362ccfSAhmed Karaman# Example of usage: 145c362ccfSAhmed Karaman# topN_callgrind.py -n 20 -- qemu-arm coulomb_double-arm 155c362ccfSAhmed Karaman# 165c362ccfSAhmed Karaman# This file is a part of the project "TCG Continuous Benchmarking". 175c362ccfSAhmed Karaman# 185c362ccfSAhmed Karaman# Copyright (C) 2020 Ahmed Karaman <ahmedkhaledkaraman@gmail.com> 195c362ccfSAhmed Karaman# Copyright (C) 2020 Aleksandar Markovic <aleksandar.qemu.devel@gmail.com> 205c362ccfSAhmed Karaman# 215c362ccfSAhmed Karaman# This program is free software: you can redistribute it and/or modify 225c362ccfSAhmed Karaman# it under the terms of the GNU General Public License as published by 235c362ccfSAhmed Karaman# the Free Software Foundation, either version 2 of the License, or 245c362ccfSAhmed Karaman# (at your option) any later version. 255c362ccfSAhmed Karaman# 265c362ccfSAhmed Karaman# This program is distributed in the hope that it will be useful, 275c362ccfSAhmed Karaman# but WITHOUT ANY WARRANTY; without even the implied warranty of 285c362ccfSAhmed Karaman# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 295c362ccfSAhmed Karaman# GNU General Public License for more details. 305c362ccfSAhmed Karaman# 315c362ccfSAhmed Karaman# You should have received a copy of the GNU General Public License 325c362ccfSAhmed Karaman# along with this program. If not, see <https://www.gnu.org/licenses/>. 335c362ccfSAhmed Karaman 345c362ccfSAhmed Karamanimport argparse 355c362ccfSAhmed Karamanimport os 365c362ccfSAhmed Karamanimport subprocess 375c362ccfSAhmed Karamanimport sys 385c362ccfSAhmed Karaman 395c362ccfSAhmed Karaman 405c362ccfSAhmed Karaman# Parse the command line arguments 415c362ccfSAhmed Karamanparser = argparse.ArgumentParser( 425c362ccfSAhmed Karaman usage='topN_callgrind.py [-h] [-n] <number of displayed top functions> -- ' 435c362ccfSAhmed Karaman '<qemu executable> [<qemu executable options>] ' 445c362ccfSAhmed Karaman '<target executable> [<target executable options>]') 455c362ccfSAhmed Karaman 465c362ccfSAhmed Karamanparser.add_argument('-n', dest='top', type=int, default=25, 475c362ccfSAhmed Karaman help='Specify the number of top functions to print.') 485c362ccfSAhmed Karaman 495c362ccfSAhmed Karamanparser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS) 505c362ccfSAhmed Karaman 515c362ccfSAhmed Karamanargs = parser.parse_args() 525c362ccfSAhmed Karaman 535c362ccfSAhmed Karaman# Extract the needed variables from the args 545c362ccfSAhmed Karamancommand = args.command 555c362ccfSAhmed Karamantop = args.top 565c362ccfSAhmed Karaman 575c362ccfSAhmed Karaman# Insure that valgrind is installed 585c362ccfSAhmed Karamancheck_valgrind_presence = subprocess.run(["which", "valgrind"], 595c362ccfSAhmed Karaman stdout=subprocess.DEVNULL) 605c362ccfSAhmed Karamanif check_valgrind_presence.returncode: 615c362ccfSAhmed Karaman sys.exit("Please install valgrind before running the script!") 625c362ccfSAhmed Karaman 635c362ccfSAhmed Karaman# Run callgrind 645c362ccfSAhmed Karamancallgrind = subprocess.run(( 655c362ccfSAhmed Karaman ["valgrind", "--tool=callgrind", "--callgrind-out-file=/tmp/callgrind.data"] 665c362ccfSAhmed Karaman + command), 675c362ccfSAhmed Karaman stdout=subprocess.DEVNULL, 685c362ccfSAhmed Karaman stderr=subprocess.PIPE) 695c362ccfSAhmed Karamanif callgrind.returncode: 705c362ccfSAhmed Karaman sys.exit(callgrind.stderr.decode("utf-8")) 715c362ccfSAhmed Karaman 725c362ccfSAhmed Karaman# Save callgrind_annotate output to /tmp/callgrind_annotate.out 735c362ccfSAhmed Karamanwith open("/tmp/callgrind_annotate.out", "w") as output: 745c362ccfSAhmed Karaman callgrind_annotate = subprocess.run(["callgrind_annotate", 755c362ccfSAhmed Karaman "/tmp/callgrind.data"], 765c362ccfSAhmed Karaman stdout=output, 775c362ccfSAhmed Karaman stderr=subprocess.PIPE) 785c362ccfSAhmed Karaman if callgrind_annotate.returncode: 795c362ccfSAhmed Karaman os.unlink('/tmp/callgrind.data') 805c362ccfSAhmed Karaman output.close() 815c362ccfSAhmed Karaman os.unlink('/tmp/callgrind_annotate.out') 825c362ccfSAhmed Karaman sys.exit(callgrind_annotate.stderr.decode("utf-8")) 835c362ccfSAhmed Karaman 845c362ccfSAhmed Karaman# Read the callgrind_annotate output to callgrind_data[] 855c362ccfSAhmed Karamancallgrind_data = [] 865c362ccfSAhmed Karamanwith open('/tmp/callgrind_annotate.out', 'r') as data: 875c362ccfSAhmed Karaman callgrind_data = data.readlines() 885c362ccfSAhmed Karaman 895c362ccfSAhmed Karaman# Line number with the total number of instructions 905c362ccfSAhmed Karamantotal_instructions_line_number = 20 915c362ccfSAhmed Karaman 925c362ccfSAhmed Karaman# Get the total number of instructions 935c362ccfSAhmed Karamantotal_instructions_line_data = callgrind_data[total_instructions_line_number] 945c362ccfSAhmed Karamantotal_number_of_instructions = total_instructions_line_data.split(' ')[0] 955c362ccfSAhmed Karamantotal_number_of_instructions = int( 965c362ccfSAhmed Karaman total_number_of_instructions.replace(',', '')) 975c362ccfSAhmed Karaman 985c362ccfSAhmed Karaman# Line number with the top function 995c362ccfSAhmed Karamanfirst_func_line = 25 1005c362ccfSAhmed Karaman 1015c362ccfSAhmed Karaman# Number of functions recorded by callgrind, last two lines are always empty 1025c362ccfSAhmed Karamannumber_of_functions = len(callgrind_data) - first_func_line - 2 1035c362ccfSAhmed Karaman 1045c362ccfSAhmed Karaman# Limit the number of top functions to "top" 1055c362ccfSAhmed Karamannumber_of_top_functions = (top if number_of_functions > 1065c362ccfSAhmed Karaman top else number_of_functions) 1075c362ccfSAhmed Karaman 1085c362ccfSAhmed Karaman# Store the data of the top functions in top_functions[] 1095c362ccfSAhmed Karamantop_functions = callgrind_data[first_func_line: 1105c362ccfSAhmed Karaman first_func_line + number_of_top_functions] 1115c362ccfSAhmed Karaman 1125c362ccfSAhmed Karaman# Print table header 1135c362ccfSAhmed Karamanprint('{:>4} {:>10} {:<30} {}\n{} {} {} {}'.format('No.', 1145c362ccfSAhmed Karaman 'Percentage', 1155c362ccfSAhmed Karaman 'Function Name', 1165c362ccfSAhmed Karaman 'Source File', 1175c362ccfSAhmed Karaman '-' * 4, 1185c362ccfSAhmed Karaman '-' * 10, 1195c362ccfSAhmed Karaman '-' * 30, 1205c362ccfSAhmed Karaman '-' * 30, 1215c362ccfSAhmed Karaman )) 1225c362ccfSAhmed Karaman 1235c362ccfSAhmed Karaman# Print top N functions 1245c362ccfSAhmed Karamanfor (index, function) in enumerate(top_functions, start=1): 1255c362ccfSAhmed Karaman function_data = function.split() 1265c362ccfSAhmed Karaman # Calculate function percentage 1275c362ccfSAhmed Karaman function_instructions = float(function_data[0].replace(',', '')) 1285c362ccfSAhmed Karaman function_percentage = (function_instructions / 1295c362ccfSAhmed Karaman total_number_of_instructions)*100 1305c362ccfSAhmed Karaman # Get function name and source files path 1315c362ccfSAhmed Karaman function_source_file, function_name = function_data[1].split(':') 1325c362ccfSAhmed Karaman # Print extracted data 1335c362ccfSAhmed Karaman print('{:>4} {:>9.3f}% {:<30} {}'.format(index, 1345c362ccfSAhmed Karaman round(function_percentage, 3), 1355c362ccfSAhmed Karaman function_name, 1365c362ccfSAhmed Karaman function_source_file)) 1375c362ccfSAhmed Karaman 1385c362ccfSAhmed Karaman# Remove intermediate files 1395c362ccfSAhmed Karamanos.unlink('/tmp/callgrind.data') 1405c362ccfSAhmed Karamanos.unlink('/tmp/callgrind_annotate.out') 141