[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:
rmcilroy 2016-05-10 08:02:46 -07:00 committed by Commit bot
parent da16609c14
commit 24709a62ce
3 changed files with 261 additions and 1 deletions

View 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()

View 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()

View File

@ -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