175 lines
4.9 KiB
Python
175 lines
4.9 KiB
Python
|
#! /usr/bin/python2
|
||
|
#
|
||
|
# Copyright 2016 the V8 project authors. All rights reserved.
|
||
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
# found in the LICENSE file.
|
||
|
#
|
||
|
|
||
|
import argparse
|
||
|
import collections
|
||
|
import os
|
||
|
import subprocess
|
||
|
import sys
|
||
|
|
||
|
|
||
|
__DESCRIPTION = """
|
||
|
Processes a perf.data sample file and annotates the hottest instructions in a
|
||
|
given bytecode handler.
|
||
|
"""
|
||
|
|
||
|
|
||
|
__HELP_EPILOGUE = """
|
||
|
Note:
|
||
|
This tool uses the disassembly of interpreter's bytecode handler codegen
|
||
|
from out/<arch>.debug/d8. you should ensure that this binary is in-sync with
|
||
|
the version used to generate the perf profile.
|
||
|
|
||
|
Also, the tool depends on the symbol offsets from perf samples being accurate.
|
||
|
As such, you should use the ":pp" suffix for events.
|
||
|
|
||
|
Examples:
|
||
|
EVENT_TYPE=cycles:pp tools/run-perf.sh out/x64.release/d8
|
||
|
tools/ignition/linux_perf_bytecode_annotate.py Add
|
||
|
"""
|
||
|
|
||
|
|
||
|
def bytecode_offset_generator(perf_stream, bytecode_name):
|
||
|
skip_until_end_of_chain = False
|
||
|
bytecode_symbol = "BytecodeHandler:" + bytecode_name;
|
||
|
|
||
|
for line in perf_stream:
|
||
|
# Lines starting with a "#" are comments, skip them.
|
||
|
if line[0] == "#":
|
||
|
continue
|
||
|
line = line.strip()
|
||
|
|
||
|
# Empty line signals the end of the callchain.
|
||
|
if not line:
|
||
|
skip_until_end_of_chain = False
|
||
|
continue
|
||
|
|
||
|
if skip_until_end_of_chain:
|
||
|
continue
|
||
|
|
||
|
symbol_and_offset = line.split(" ", 1)[1]
|
||
|
|
||
|
if symbol_and_offset.startswith("BytecodeHandler:"):
|
||
|
skip_until_end_of_chain = True
|
||
|
|
||
|
if symbol_and_offset.startswith(bytecode_symbol):
|
||
|
yield int(symbol_and_offset.split("+", 1)[1], 16)
|
||
|
|
||
|
|
||
|
def bytecode_offset_counts(bytecode_offsets):
|
||
|
offset_counts = collections.defaultdict(int)
|
||
|
for offset in bytecode_offsets:
|
||
|
offset_counts[offset] += 1
|
||
|
return offset_counts
|
||
|
|
||
|
|
||
|
def bytecode_disassembly_generator(ignition_codegen, bytecode_name):
|
||
|
name_string = "name = " + bytecode_name
|
||
|
for line in ignition_codegen:
|
||
|
if line.startswith(name_string):
|
||
|
break
|
||
|
|
||
|
# Found the bytecode disassembly.
|
||
|
for line in ignition_codegen:
|
||
|
line = line.strip()
|
||
|
# Blank line marks the end of the bytecode's disassembly.
|
||
|
if not line:
|
||
|
return
|
||
|
|
||
|
# Only yield disassembly output.
|
||
|
if not line.startswith("0x"):
|
||
|
continue
|
||
|
|
||
|
yield line
|
||
|
|
||
|
|
||
|
def print_disassembly_annotation(offset_counts, bytecode_disassembly):
|
||
|
total = sum(offset_counts.values())
|
||
|
offsets = sorted(offset_counts, reverse=True)
|
||
|
def next_offset():
|
||
|
return offsets.pop() if offsets else -1
|
||
|
|
||
|
current_offset = next_offset()
|
||
|
print current_offset;
|
||
|
|
||
|
for line in bytecode_disassembly:
|
||
|
disassembly_offset = int(line.split()[1])
|
||
|
if disassembly_offset == current_offset:
|
||
|
count = offset_counts[current_offset]
|
||
|
percentage = 100.0 * count / total
|
||
|
print "{:>8d} ({:>5.1f}%) ".format(count, percentage),
|
||
|
current_offset = next_offset()
|
||
|
else:
|
||
|
print " ",
|
||
|
print line
|
||
|
|
||
|
if offsets:
|
||
|
print ("WARNING: Offsets not empty. Output is most likely invalid due to "
|
||
|
"a mismatch between perf output and debug d8 binary.")
|
||
|
|
||
|
|
||
|
def parse_command_line():
|
||
|
command_line_parser = argparse.ArgumentParser(
|
||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
|
description=__DESCRIPTION,
|
||
|
epilog=__HELP_EPILOGUE)
|
||
|
|
||
|
command_line_parser.add_argument(
|
||
|
"--arch", "-a",
|
||
|
help="The architecture (default: x64)",
|
||
|
default="x64",
|
||
|
)
|
||
|
command_line_parser.add_argument(
|
||
|
"--input", "-i",
|
||
|
help="perf sample file to process (default: perf.data)",
|
||
|
default="perf.data",
|
||
|
metavar="<perf filename>",
|
||
|
dest="perf_filename"
|
||
|
)
|
||
|
command_line_parser.add_argument(
|
||
|
"--output", "-o",
|
||
|
help="output file name (stdout if omitted)",
|
||
|
type=argparse.FileType("wt"),
|
||
|
default=sys.stdout,
|
||
|
metavar="<output filename>",
|
||
|
dest="output_stream"
|
||
|
)
|
||
|
command_line_parser.add_argument(
|
||
|
"bytecode_name",
|
||
|
metavar="<bytecode name>",
|
||
|
nargs="?",
|
||
|
help="The bytecode handler to annotate"
|
||
|
)
|
||
|
|
||
|
return command_line_parser.parse_args()
|
||
|
|
||
|
|
||
|
def main():
|
||
|
program_options = parse_command_line()
|
||
|
perf = subprocess.Popen(["perf", "script", "-f", "ip,sym,symoff",
|
||
|
"-i", program_options.perf_filename],
|
||
|
stdout=subprocess.PIPE)
|
||
|
|
||
|
v8_root_path = os.path.dirname(__file__) + "/../../"
|
||
|
d8_path = "{}/out/{}.debug/d8".format(v8_root_path, program_options.arch)
|
||
|
d8_codegen = subprocess.Popen([d8_path, "--ignition",
|
||
|
"--trace-ignition-codegen", "-e", "1"],
|
||
|
stdout=subprocess.PIPE)
|
||
|
|
||
|
bytecode_offsets = bytecode_offset_generator(
|
||
|
perf.stdout, program_options.bytecode_name)
|
||
|
offset_counts = bytecode_offset_counts(bytecode_offsets)
|
||
|
|
||
|
bytecode_disassembly = bytecode_disassembly_generator(
|
||
|
d8_codegen.stdout, program_options.bytecode_name)
|
||
|
|
||
|
print_disassembly_annotation(offset_counts, bytecode_disassembly)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|