summaryrefslogtreecommitdiffstats
path: root/scripts/performance/dissect.py
blob: bf24f5092254ffee900bdfa7f2b3cf808da6402b (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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python3

#  Print the percentage of instructions spent in each phase of QEMU
#  execution.
#
#  Syntax:
#  dissect.py [-h] -- <qemu executable> [<qemu executable options>] \
#                   <target executable> [<target executable options>]
#
#  [-h] - Print the script arguments help message.
#
#  Example of usage:
#  dissect.py -- 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
import tempfile


def get_JIT_line(callgrind_data):
    """
    Search for the first instance of the JIT call in
    the callgrind_annotate output when ran using --tree=caller
    This is equivalent to the self number of instructions of JIT.

    Parameters:
    callgrind_data (list): callgrind_annotate output

    Returns:
    (int): Line number
    """
    line = -1
    for i in range(len(callgrind_data)):
        if callgrind_data[i].strip('\n') and \
                callgrind_data[i].split()[-1] == "[???]":
            line = i
            break
    if line == -1:
        sys.exit("Couldn't locate the JIT call ... Exiting.")
    return line


def main():
    # Parse the command line arguments
    parser = argparse.ArgumentParser(
        usage='dissect.py [-h] -- '
        '<qemu executable> [<qemu executable options>] '
        '<target executable> [<target executable options>]')

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

    args = parser.parse_args()

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

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

    # Save all intermediate files in a temporary directory
    with tempfile.TemporaryDirectory() as tmpdirname:
        # callgrind output file path
        data_path = os.path.join(tmpdirname, "callgrind.data")
        # callgrind_annotate output file path
        annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out")

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

        # Save callgrind_annotate output
        with open(annotate_out_path, "w") as output:
            callgrind_annotate = subprocess.run(
                ["callgrind_annotate", data_path, "--tree=caller"],
                stdout=output,
                stderr=subprocess.PIPE)
            if callgrind_annotate.returncode:
                sys.exit(callgrind_annotate.stderr.decode("utf-8"))

        # Read the callgrind_annotate output to callgrind_data[]
        callgrind_data = []
        with open(annotate_out_path, '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_instructions = total_instructions_line_data.split()[0]
        total_instructions = int(total_instructions.replace(',', ''))

        # Line number with the JIT self number of instructions
        JIT_self_instructions_line_number = get_JIT_line(callgrind_data)
        # Get the JIT self number of instructions
        JIT_self_instructions_line_data = \
            callgrind_data[JIT_self_instructions_line_number]
        JIT_self_instructions = JIT_self_instructions_line_data.split()[0]
        JIT_self_instructions = int(JIT_self_instructions.replace(',', ''))

        # Line number with the JIT self + inclusive number of instructions
        # It's the line above the first JIT call when running with --tree=caller
        JIT_total_instructions_line_number = JIT_self_instructions_line_number-1
        # Get the JIT self + inclusive number of instructions
        JIT_total_instructions_line_data = \
            callgrind_data[JIT_total_instructions_line_number]
        JIT_total_instructions = JIT_total_instructions_line_data.split()[0]
        JIT_total_instructions = int(JIT_total_instructions.replace(',', ''))

        # Calculate number of instructions in helpers and code generation
        helpers_instructions = JIT_total_instructions-JIT_self_instructions
        code_generation_instructions = total_instructions-JIT_total_instructions

        # Print results (Insert commas in large numbers)
        # Print total number of instructions
        print('{:<20}{:>20}\n'.
              format("Total Instructions:",
                     format(total_instructions, ',')))
        # Print code generation instructions and percentage
        print('{:<20}{:>20}\t{:>6.3f}%'.
              format("Code Generation:",
                     format(code_generation_instructions, ","),
                     (code_generation_instructions / total_instructions) * 100))
        # Print JIT instructions and percentage
        print('{:<20}{:>20}\t{:>6.3f}%'.
              format("JIT Execution:",
                     format(JIT_self_instructions, ","),
                     (JIT_self_instructions / total_instructions) * 100))
        # Print helpers instructions and percentage
        print('{:<20}{:>20}\t{:>6.3f}%'.
              format("Helpers:",
                     format(helpers_instructions, ","),
                     (helpers_instructions/total_instructions)*100))


if __name__ == "__main__":
    main()