summaryrefslogtreecommitdiffstats
path: root/scripts/performance/topN_perf.py
blob: 07be195fc86773f64d13a85e828c491c2417bc80 (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
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python3

#  Print the top N most executed functions in QEMU using perf.
#  Syntax:
#  topN_perf.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_perf.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_perf.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 perf is installed
check_perf_presence = subprocess.run(["which", "perf"],
                                     stdout=subprocess.DEVNULL)
if check_perf_presence.returncode:
    sys.exit("Please install perf before running the script!")

# Insure user has previllage to run perf
check_perf_executability = subprocess.run(["perf", "stat", "ls", "/"],
                                          stdout=subprocess.DEVNULL,
                                          stderr=subprocess.DEVNULL)
if check_perf_executability.returncode:
    sys.exit(
"""
Error:
You may not have permission to collect stats.

Consider tweaking /proc/sys/kernel/perf_event_paranoid,
which controls use of the performance events system by
unprivileged users (without CAP_SYS_ADMIN).

  -1: Allow use of (almost) all events by all users
      Ignore mlock limit after perf_event_mlock_kb without CAP_IPC_LOCK
   0: Disallow ftrace function tracepoint by users without CAP_SYS_ADMIN
      Disallow raw tracepoint access by users without CAP_SYS_ADMIN
   1: Disallow CPU event access by users without CAP_SYS_ADMIN
   2: Disallow kernel profiling by users without CAP_SYS_ADMIN

To make this setting permanent, edit /etc/sysctl.conf too, e.g.:
   kernel.perf_event_paranoid = -1

* Alternatively, you can run this script under sudo privileges.
"""
)

# Run perf record
perf_record = subprocess.run((["perf", "record", "--output=/tmp/perf.data"] +
                              command),
                             stdout=subprocess.DEVNULL,
                             stderr=subprocess.PIPE)
if perf_record.returncode:
    os.unlink('/tmp/perf.data')
    sys.exit(perf_record.stderr.decode("utf-8"))

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

# Read the reported data to functions[]
functions = []
with open("/tmp/perf_report.out", "r") as data:
    # Only read lines that are not comments (comments start with #)
    # Only read lines that are not empty
    functions = [line for line in data.readlines() if line and line[0]
                 != '#' and line[0] != "\n"]

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

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

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

# Print top N functions
for (index, function) in enumerate(top_functions, start=1):
    function_data = function.split()
    function_percentage = function_data[0]
    function_name = function_data[-1]
    function_invoker = ' '.join(function_data[2:-2])
    print('{:>4}  {:>10}  {:<30}  {}'.format(index,
                                             function_percentage,
                                             function_name,
                                             function_invoker))

# Remove intermediate files
os.unlink('/tmp/perf.data')
os.unlink('/tmp/perf_report.out')