#! /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()