summaryrefslogtreecommitdiffstats
path: root/scripts/performance/topN_callgrind.py
blob: 67c59197af7c37947f3192d89d8dacda0c403b2f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3

#  Print 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
#
#  This file is a part of the project "TCG Continuous Benchmarking".
#
#  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
#  Copyright (C) 2020  Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program. If not, see <https://www.gnu.org/licenses/>.

import argparse
import os
import subprocess
import sys


# Parse the command line arguments
parser = argparse.ArgumentParser(
    usage='topN_callgrind.py [-h] [-n] <number of displayed top functions>  -- '
          '<qemu executable> [<qemu executable options>] '
          '<target executable> [<target executable options>]')

parser.add_argument('-n', dest='top', type=int, default=25,
                    help='Specify the number of top functions to print.')

parser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS)

args = parser.parse_args()

# Extract the needed variables from the args
command = args.command
top = args.top

# Insure that valgrind is installed
check_valgrind_presence = subprocess.run(["which", "valgrind"],
                                         stdout=subprocess.DEVNULL)
if check_valgrind_presence.returncode:
    sys.exit("Please install valgrind before running the script!")

# Run callgrind
callgrind = subprocess.run((
    ["valgrind", "--tool=callgrind", "--callgrind-out-file=/tmp/callgrind.data"]
    + command),
    stdout=subprocess.DEVNULL,
    stderr=subprocess.PIPE)
if callgrind.returncode:
    sys.exit(callgrind.stderr.decode("utf-8"))

# Save callgrind_annotate output to /tmp/callgrind_annotate.out
with open("/tmp/callgrind_annotate.out", "w") as output:
    callgrind_annotate = subprocess.run(["callgrind_annotate",
                                         "/tmp/callgrind.data"],
                                        stdout=output,
                                        stderr=subprocess.PIPE)
    if callgrind_annotate.returncode:
        os.unlink('/tmp/callgrind.data')
        output.close()
        os.unlink('/tmp/callgrind_annotate.out')
        sys.exit(callgrind_annotate.stderr.decode("utf-8"))

# Read the callgrind_annotate output to callgrind_data[]
callgrind_data = []
with open('/tmp/callgrind_annotate.out', 'r') as data:
    callgrind_data = data.readlines()

# Line number with the total number of instructions
total_instructions_line_number = 20

# Get the total number of instructions
total_instructions_line_data = callgrind_data[total_instructions_line_number]
total_number_of_instructions = total_instructions_line_data.split(' ')[0]
total_number_of_instructions = int(
    total_number_of_instructions.replace(',', ''))

# Line number with the top function
first_func_line = 25

# Number of functions recorded by callgrind, last two lines are always empty
number_of_functions = len(callgrind_data) - first_func_line - 2

# Limit the number of top functions to "top"
number_of_top_functions = (top if number_of_functions >
                           top else number_of_functions)

# Store the data of the top functions in top_functions[]
top_functions = callgrind_data[first_func_line:
                               first_func_line + number_of_top_functions]

# Print table header
print('{:>4}  {:>10}  {:<30}  {}\n{}  {}  {}  {}'.format('No.',
                                                         'Percentage',
                                                         'Function Name',
                                                         'Source File',
                                                         '-' * 4,
                                                         '-' * 10,
                                                         '-' * 30,
                                                         '-' * 30,
                                                         ))

# Print top N functions
for (index, function) in enumerate(top_functions, start=1):
    function_data = function.split()
    # Calculate function percentage
    function_instructions = float(function_data[0].replace(',', ''))
    function_percentage = (function_instructions /
                           total_number_of_instructions)*100
    # Get function name and source files path
    function_source_file, function_name = function_data[1].split(':')
    # Print extracted data
    print('{:>4}  {:>9.3f}%  {:<30}  {}'.format(index,
                                                round(function_percentage, 3),
                                                function_name,
                                                function_source_file))

# Remove intermediate files
os.unlink('/tmp/callgrind.data')
os.unlink('/tmp/callgrind_annotate.out')