qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

dissect.py (6718B)


      1 #!/usr/bin/env python3
      2 
      3 #  Print the percentage of instructions spent in each phase of QEMU
      4 #  execution.
      5 #
      6 #  Syntax:
      7 #  dissect.py [-h] -- <qemu executable> [<qemu executable options>] \
      8 #                   <target executable> [<target executable options>]
      9 #
     10 #  [-h] - Print the script arguments help message.
     11 #
     12 #  Example of usage:
     13 #  dissect.py -- qemu-arm coulomb_double-arm
     14 #
     15 #  This file is a part of the project "TCG Continuous Benchmarking".
     16 #
     17 #  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
     18 #  Copyright (C) 2020  Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>
     19 #
     20 #  This program is free software: you can redistribute it and/or modify
     21 #  it under the terms of the GNU General Public License as published by
     22 #  the Free Software Foundation, either version 2 of the License, or
     23 #  (at your option) any later version.
     24 #
     25 #  This program is distributed in the hope that it will be useful,
     26 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
     27 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     28 #  GNU General Public License for more details.
     29 #
     30 #  You should have received a copy of the GNU General Public License
     31 #  along with this program. If not, see <https://www.gnu.org/licenses/>.
     32 
     33 import argparse
     34 import os
     35 import subprocess
     36 import sys
     37 import tempfile
     38 
     39 
     40 def get_JIT_line(callgrind_data):
     41     """
     42     Search for the first instance of the JIT call in
     43     the callgrind_annotate output when ran using --tree=caller
     44     This is equivalent to the self number of instructions of JIT.
     45 
     46     Parameters:
     47     callgrind_data (list): callgrind_annotate output
     48 
     49     Returns:
     50     (int): Line number
     51     """
     52     line = -1
     53     for i in range(len(callgrind_data)):
     54         if callgrind_data[i].strip('\n') and \
     55                 callgrind_data[i].split()[-1] == "[???]":
     56             line = i
     57             break
     58     if line == -1:
     59         sys.exit("Couldn't locate the JIT call ... Exiting.")
     60     return line
     61 
     62 
     63 def main():
     64     # Parse the command line arguments
     65     parser = argparse.ArgumentParser(
     66         usage='dissect.py [-h] -- '
     67         '<qemu executable> [<qemu executable options>] '
     68         '<target executable> [<target executable options>]')
     69 
     70     parser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS)
     71 
     72     args = parser.parse_args()
     73 
     74     # Extract the needed variables from the args
     75     command = args.command
     76 
     77     # Insure that valgrind is installed
     78     check_valgrind = subprocess.run(
     79         ["which", "valgrind"], stdout=subprocess.DEVNULL)
     80     if check_valgrind.returncode:
     81         sys.exit("Please install valgrind before running the script.")
     82 
     83     # Save all intermediate files in a temporary directory
     84     with tempfile.TemporaryDirectory() as tmpdirname:
     85         # callgrind output file path
     86         data_path = os.path.join(tmpdirname, "callgrind.data")
     87         # callgrind_annotate output file path
     88         annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out")
     89 
     90         # Run callgrind
     91         callgrind = subprocess.run((["valgrind",
     92                                      "--tool=callgrind",
     93                                      "--callgrind-out-file=" + data_path]
     94                                     + command),
     95                                    stdout=subprocess.DEVNULL,
     96                                    stderr=subprocess.PIPE)
     97         if callgrind.returncode:
     98             sys.exit(callgrind.stderr.decode("utf-8"))
     99 
    100         # Save callgrind_annotate output
    101         with open(annotate_out_path, "w") as output:
    102             callgrind_annotate = subprocess.run(
    103                 ["callgrind_annotate", data_path, "--tree=caller"],
    104                 stdout=output,
    105                 stderr=subprocess.PIPE)
    106             if callgrind_annotate.returncode:
    107                 sys.exit(callgrind_annotate.stderr.decode("utf-8"))
    108 
    109         # Read the callgrind_annotate output to callgrind_data[]
    110         callgrind_data = []
    111         with open(annotate_out_path, 'r') as data:
    112             callgrind_data = data.readlines()
    113 
    114         # Line number with the total number of instructions
    115         total_instructions_line_number = 20
    116         # Get the total number of instructions
    117         total_instructions_line_data = \
    118             callgrind_data[total_instructions_line_number]
    119         total_instructions = total_instructions_line_data.split()[0]
    120         total_instructions = int(total_instructions.replace(',', ''))
    121 
    122         # Line number with the JIT self number of instructions
    123         JIT_self_instructions_line_number = get_JIT_line(callgrind_data)
    124         # Get the JIT self number of instructions
    125         JIT_self_instructions_line_data = \
    126             callgrind_data[JIT_self_instructions_line_number]
    127         JIT_self_instructions = JIT_self_instructions_line_data.split()[0]
    128         JIT_self_instructions = int(JIT_self_instructions.replace(',', ''))
    129 
    130         # Line number with the JIT self + inclusive number of instructions
    131         # It's the line above the first JIT call when running with --tree=caller
    132         JIT_total_instructions_line_number = JIT_self_instructions_line_number-1
    133         # Get the JIT self + inclusive number of instructions
    134         JIT_total_instructions_line_data = \
    135             callgrind_data[JIT_total_instructions_line_number]
    136         JIT_total_instructions = JIT_total_instructions_line_data.split()[0]
    137         JIT_total_instructions = int(JIT_total_instructions.replace(',', ''))
    138 
    139         # Calculate number of instructions in helpers and code generation
    140         helpers_instructions = JIT_total_instructions-JIT_self_instructions
    141         code_generation_instructions = total_instructions-JIT_total_instructions
    142 
    143         # Print results (Insert commas in large numbers)
    144         # Print total number of instructions
    145         print('{:<20}{:>20}\n'.
    146               format("Total Instructions:",
    147                      format(total_instructions, ',')))
    148         # Print code generation instructions and percentage
    149         print('{:<20}{:>20}\t{:>6.3f}%'.
    150               format("Code Generation:",
    151                      format(code_generation_instructions, ","),
    152                      (code_generation_instructions / total_instructions) * 100))
    153         # Print JIT instructions and percentage
    154         print('{:<20}{:>20}\t{:>6.3f}%'.
    155               format("JIT Execution:",
    156                      format(JIT_self_instructions, ","),
    157                      (JIT_self_instructions / total_instructions) * 100))
    158         # Print helpers instructions and percentage
    159         print('{:<20}{:>20}\t{:>6.3f}%'.
    160               format("Helpers:",
    161                      format(helpers_instructions, ","),
    162                      (helpers_instructions/total_instructions)*100))
    163 
    164 
    165 if __name__ == "__main__":
    166     main()