[Interpreter] Add a bytecode annotate tool.
Adds a tool which enables annotation of the disassembly of bytecode handlers based on perf output. BUG=4899 LOG=N Review-Url: https://codereview.chromium.org/1945673002 Cr-Commit-Position: refs/heads/master@{#36145}
This commit is contained in:
parent
da16609c14
commit
24709a62ce
174
tools/ignition/linux_perf_bytecode_annotate.py
Executable file
174
tools/ignition/linux_perf_bytecode_annotate.py
Executable file
@ -0,0 +1,174 @@
|
||||
#! /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()
|
85
tools/ignition/linux_perf_bytecode_annotate_test.py
Normal file
85
tools/ignition/linux_perf_bytecode_annotate_test.py
Normal file
@ -0,0 +1,85 @@
|
||||
# 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 StringIO
|
||||
import unittest
|
||||
import linux_perf_bytecode_annotate as bytecode_annotate
|
||||
|
||||
|
||||
PERF_SCRIPT_OUTPUT = """
|
||||
# This line is a comment
|
||||
# This should be ignored too
|
||||
#
|
||||
# cdefab01 aRandomSymbol::Name(to, be, ignored)
|
||||
|
||||
00000000 firstSymbol
|
||||
00000123 secondSymbol
|
||||
|
||||
01234567 foo
|
||||
abcdef76 BytecodeHandler:bar+0x12
|
||||
76543210 baz
|
||||
abcdef76 BytecodeHandler:bar+0x16
|
||||
76543210 baz
|
||||
|
||||
01234567 foo
|
||||
abcdef76 BytecodeHandler:foo+0x1
|
||||
76543210 baz
|
||||
abcdef76 BytecodeHandler:bar+0x2
|
||||
76543210 bar
|
||||
|
||||
abcdef76 BytecodeHandler:bar+0x19
|
||||
|
||||
abcdef76 BytecodeHandler:bar+0x12
|
||||
|
||||
abcdef76 BytecodeHandler:bar+0x12
|
||||
"""
|
||||
|
||||
|
||||
D8_CODEGEN_OUTPUT = """
|
||||
kind = BYTECODE_HANDLER
|
||||
name = foo
|
||||
compiler = turbofan
|
||||
Instructions (size = 3)
|
||||
0x3101394a3c0 0 55 push rbp
|
||||
0x3101394a3c1 1 ffe3 jmp rbx
|
||||
|
||||
kind = BYTECODE_HANDLER
|
||||
name = bar
|
||||
compiler = turbofan
|
||||
Instructions (size = 5)
|
||||
0x3101394b3c0 0 55 push rbp
|
||||
0x3101394b3c1 1 4883c428 REX.W addq rsp,0x28
|
||||
# Unexpected comment
|
||||
0x3101394b3c5 5 ffe3 jmp rbx
|
||||
|
||||
kind = BYTECODE_HANDLER
|
||||
name = baz
|
||||
compiler = turbofan
|
||||
Instructions (size = 5)
|
||||
0x3101394c3c0 0 55 push rbp
|
||||
0x3101394c3c1 1 4883c428 REX.W addq rsp,0x28
|
||||
0x3101394c3c5 5 ffe3 jmp rbx
|
||||
"""
|
||||
|
||||
|
||||
class LinuxPerfBytecodeAnnotateTest(unittest.TestCase):
|
||||
|
||||
def test_bytecode_offset_generator(self):
|
||||
perf_stream = StringIO.StringIO(PERF_SCRIPT_OUTPUT)
|
||||
offsets = list(
|
||||
bytecode_annotate.bytecode_offset_generator(perf_stream, "bar"))
|
||||
self.assertListEqual(offsets, [18, 25, 18, 18])
|
||||
|
||||
def test_bytecode_disassembly_generator(self):
|
||||
codegen_stream = StringIO.StringIO(D8_CODEGEN_OUTPUT)
|
||||
disassembly = list(
|
||||
bytecode_annotate.bytecode_disassembly_generator(codegen_stream, "bar"))
|
||||
self.assertListEqual(disassembly, [
|
||||
"0x3101394b3c0 0 55 push rbp",
|
||||
"0x3101394b3c1 1 4883c428 REX.W addq rsp,0x28",
|
||||
"0x3101394b3c5 5 ffe3 jmp rbx"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -13,6 +13,7 @@ SAMPLE_EVERY_N_CYCLES=10000
|
||||
SAMPLE_RATE_CONFIG_FILE="/proc/sys/kernel/perf_event_max_sample_rate"
|
||||
KERNEL_MAP_CONFIG_FILE="/proc/sys/kernel/kptr_restrict"
|
||||
CALL_GRAPH_METHOD="fp" # dwarf does not play nice with JITted objects.
|
||||
EVENT_TYPE=${EVENT_TYPE:=cycles:u}
|
||||
|
||||
########## Usage
|
||||
|
||||
@ -46,7 +47,7 @@ fi
|
||||
|
||||
echo "Running..."
|
||||
perf record -R \
|
||||
-e cycles:u \
|
||||
-e $EVENT_TYPE \
|
||||
-c $SAMPLE_EVERY_N_CYCLES \
|
||||
--call-graph $CALL_GRAPH_METHOD \
|
||||
-i $@ --perf_basic_prof
|
||||
|
Loading…
Reference in New Issue
Block a user