xref: /openbmc/qemu/scripts/performance/topN_callgrind.py (revision c5ea91da443b458352c1b629b490ee6631775cb4)
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