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 executable 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')
|