Commit 87562a70 authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[tools] Remove obsolete ignition profiling tools

These tools haven't been used in a while and are unmaintained.
We can use pprof with stack-filtering to achieve similar results.

Change-Id: I84392c066dffc6b0d9efba27a8fdfb31091796bb
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3593786Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80026}
parent e2679ec7
#! /usr/bin/python
#
# 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.
#
# for py2/py3 compatibility
from __future__ import print_function
import argparse
import heapq
import json
from matplotlib import colors
from matplotlib import pyplot
import numpy
import struct
import sys
__DESCRIPTION = """
Process v8.ignition_dispatches_counters.json and list top counters,
or plot a dispatch heatmap.
Please note that those handlers that may not or will never dispatch
(e.g. Return or Throw) do not show up in the results.
"""
__HELP_EPILOGUE = """
examples:
# Print the hottest bytecodes in descending order, reading from
# default filename v8.ignition_dispatches_counters.json (default mode)
$ tools/ignition/bytecode_dispatches_report.py
# Print the hottest 15 bytecode dispatch pairs reading from data.json
$ tools/ignition/bytecode_dispatches_report.py -t -n 15 data.json
# Save heatmap to default filename v8.ignition_dispatches_counters.svg
$ tools/ignition/bytecode_dispatches_report.py -p
# Save heatmap to filename data.svg
$ tools/ignition/bytecode_dispatches_report.py -p -o data.svg
# Open the heatmap in an interactive viewer
$ tools/ignition/bytecode_dispatches_report.py -p -i
# Display the top 5 sources and destinations of dispatches to/from LdaZero
$ tools/ignition/bytecode_dispatches_report.py -f LdaZero -n 5
"""
__COUNTER_BITS = struct.calcsize("P") * 8 # Size in bits of a pointer
__COUNTER_MAX = 2**__COUNTER_BITS - 1
def warn_if_counter_may_have_saturated(dispatches_table):
for source, counters_from_source in iteritems(dispatches_table):
for destination, counter in iteritems(counters_from_source):
if counter == __COUNTER_MAX:
print("WARNING: {} -> {} may have saturated.".format(source,
destination))
def find_top_bytecode_dispatch_pairs(dispatches_table, top_count):
def flattened_counters_generator():
for source, counters_from_source in iteritems(dispatches_table):
for destination, counter in iteritems(counters_from_source):
yield source, destination, counter
return heapq.nlargest(top_count, flattened_counters_generator(),
key=lambda x: x[2])
def print_top_bytecode_dispatch_pairs(dispatches_table, top_count):
top_bytecode_dispatch_pairs = (
find_top_bytecode_dispatch_pairs(dispatches_table, top_count))
print("Top {} bytecode dispatch pairs:".format(top_count))
for source, destination, counter in top_bytecode_dispatch_pairs:
print("{:>12d}\t{} -> {}".format(counter, source, destination))
def find_top_bytecodes(dispatches_table):
top_bytecodes = []
for bytecode, counters_from_bytecode in iteritems(dispatches_table):
top_bytecodes.append((bytecode, sum(itervalues(counters_from_bytecode))))
top_bytecodes.sort(key=lambda x: x[1], reverse=True)
return top_bytecodes
def print_top_bytecodes(dispatches_table):
top_bytecodes = find_top_bytecodes(dispatches_table)
print("Top bytecodes:")
for bytecode, counter in top_bytecodes:
print("{:>12d}\t{}".format(counter, bytecode))
def find_top_dispatch_sources_and_destinations(
dispatches_table, bytecode, top_count, sort_source_relative):
sources = []
for source, destinations in iteritems(dispatches_table):
total = float(sum(itervalues(destinations)))
if bytecode in destinations:
count = destinations[bytecode]
sources.append((source, count, count / total))
destinations = []
bytecode_destinations = dispatches_table[bytecode]
bytecode_total = float(sum(itervalues(bytecode_destinations)))
for destination, count in iteritems(bytecode_destinations):
destinations.append((destination, count, count / bytecode_total))
return (heapq.nlargest(top_count, sources,
key=lambda x: x[2 if sort_source_relative else 1]),
heapq.nlargest(top_count, destinations, key=lambda x: x[1]))
def print_top_dispatch_sources_and_destinations(dispatches_table, bytecode,
top_count, sort_relative):
top_sources, top_destinations = find_top_dispatch_sources_and_destinations(
dispatches_table, bytecode, top_count, sort_relative)
print("Top sources of dispatches to {}:".format(bytecode))
for source_name, counter, ratio in top_sources:
print("{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, source_name))
print("\nTop destinations of dispatches from {}:".format(bytecode))
for destination_name, counter, ratio in top_destinations:
print("{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, destination_name))
def build_counters_matrix(dispatches_table):
labels = sorted(dispatches_table.keys())
counters_matrix = numpy.empty([len(labels), len(labels)], dtype=int)
for from_index, from_name in enumerate(labels):
current_row = dispatches_table[from_name];
for to_index, to_name in enumerate(labels):
counters_matrix[from_index, to_index] = current_row.get(to_name, 0)
# Reverse y axis for a nicer appearance
xlabels = labels
ylabels = list(reversed(xlabels))
counters_matrix = numpy.flipud(counters_matrix)
return counters_matrix, xlabels, ylabels
def plot_dispatches_table(dispatches_table, figure, axis):
counters_matrix, xlabels, ylabels = build_counters_matrix(dispatches_table)
image = axis.pcolor(
counters_matrix,
cmap="jet",
norm=colors.LogNorm(),
edgecolor="grey",
linestyle="dotted",
linewidth=0.5
)
axis.xaxis.set(
ticks=numpy.arange(0.5, len(xlabels)),
label="From bytecode handler"
)
axis.xaxis.tick_top()
axis.set_xlim(0, len(xlabels))
axis.set_xticklabels(xlabels, rotation="vertical")
axis.yaxis.set(
ticks=numpy.arange(0.5, len(ylabels)),
label="To bytecode handler",
ticklabels=ylabels
)
axis.set_ylim(0, len(ylabels))
figure.colorbar(
image,
ax=axis,
fraction=0.01,
pad=0.01
)
def parse_command_line():
command_line_parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__DESCRIPTION,
epilog=__HELP_EPILOGUE
)
command_line_parser.add_argument(
"--plot-size", "-s",
metavar="N",
default=30,
help="shorter side in inches of the output plot (default 30)"
)
command_line_parser.add_argument(
"--plot", "-p",
action="store_true",
help="plot dispatch pairs heatmap"
)
command_line_parser.add_argument(
"--interactive", "-i",
action="store_true",
help="open the heatmap in an interactive viewer, instead of writing to file"
)
command_line_parser.add_argument(
"--top-bytecode-dispatch-pairs", "-t",
action="store_true",
help="print the top bytecode dispatch pairs"
)
command_line_parser.add_argument(
"--top-entries-count", "-n",
metavar="N",
type=int,
default=10,
help="print N top entries when running with -t or -f (default 10)"
)
command_line_parser.add_argument(
"--top-dispatches-for-bytecode", "-f",
metavar="<bytecode name>",
help="print top dispatch sources and destinations to the specified bytecode"
)
command_line_parser.add_argument(
"--output-filename", "-o",
metavar="<output filename>",
default="v8.ignition_dispatches_table.svg",
help=("file to save the plot file to. File type is deduced from the "
"extension. PDF, SVG, PNG supported")
)
command_line_parser.add_argument(
"--sort-sources-relative", "-r",
action="store_true",
help=("print top sources in order to how often they dispatch to the "
"specified bytecode, only applied when using -f")
)
command_line_parser.add_argument(
"input_filename",
metavar="<input filename>",
default="v8.ignition_dispatches_table.json",
nargs='?',
help="Ignition counters JSON file"
)
return command_line_parser.parse_args()
def itervalues(d):
return d.values() if sys.version_info[0] > 2 else d.itervalues()
def iteritems(d):
return d.items() if sys.version_info[0] > 2 else d.iteritems()
def main():
program_options = parse_command_line()
with open(program_options.input_filename) as stream:
dispatches_table = json.load(stream)
warn_if_counter_may_have_saturated(dispatches_table)
if program_options.plot:
figure, axis = pyplot.subplots()
plot_dispatches_table(dispatches_table, figure, axis)
if program_options.interactive:
pyplot.show()
else:
figure.set_size_inches(program_options.plot_size,
program_options.plot_size)
pyplot.savefig(program_options.output_filename)
elif program_options.top_bytecode_dispatch_pairs:
print_top_bytecode_dispatch_pairs(
dispatches_table, program_options.top_entries_count)
elif program_options.top_dispatches_for_bytecode:
print_top_dispatch_sources_and_destinations(
dispatches_table, program_options.top_dispatches_for_bytecode,
program_options.top_entries_count, program_options.sort_sources_relative)
else:
print_top_bytecodes(dispatches_table)
if __name__ == "__main__":
main()
# 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 bytecode_dispatches_report as bdr
import unittest
class BytecodeDispatchesReportTest(unittest.TestCase):
def test_find_top_counters(self):
top_counters = bdr.find_top_bytecode_dispatch_pairs({
"a": {"a": 10, "b": 8, "c": 99},
"b": {"a": 1, "b": 4, "c": 1},
"c": {"a": 42, "b": 3, "c": 7}}, 5)
self.assertListEqual(top_counters, [
('a', 'c', 99),
('c', 'a', 42),
('a', 'a', 10),
('a', 'b', 8),
('c', 'c', 7)])
def test_build_counters_matrix(self):
counters_matrix, xlabels, ylabels = bdr.build_counters_matrix({
"a": {"a": 10, "b": 8, "c": 7},
"b": {"a": 1, "c": 4},
"c": {"a": 42, "b": 12, "c": 99}})
self.assertTrue((counters_matrix == [[42, 12, 99],
[ 1, 0, 4],
[10, 8, 7]]).all())
self.assertListEqual(xlabels, ['a', 'b', 'c'])
self.assertListEqual(ylabels, ['c', 'b', 'a'])
def test_find_top_bytecodes(self):
top_dispatch_sources = bdr.find_top_bytecodes({
"a": {"a": 10, "b": 8, "c": 7},
"b": {"a": 1, "c": 4},
"c": {"a": 42, "b": 12, "c": 99}
})
self.assertListEqual(top_dispatch_sources, [
('c', 153),
('a', 25),
('b', 5)
])
def test_find_top_dispatch_sources_and_destinations(self):
d = {
"a": {"a": 4, "b": 2, "c": 4},
"b": {"a": 1, "c": 4},
"c": {"a": 40, "b": 10, "c": 50}
}
top_sources, top_dests = bdr.find_top_dispatch_sources_and_destinations(
d, "b", 10, False)
self.assertListEqual(top_sources, [
("c", 10, 0.1),
("a", 2, 0.2)
])
top_sources, top_dests = bdr.find_top_dispatch_sources_and_destinations(
d, "b", 10, True)
self.assertListEqual(top_sources, [
("a", 2, 0.2),
("c", 10, 0.1)
])
#! /usr/bin/python
#
# 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.
#
# for py2/py3 compatibility
from __future__ import print_function
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), end=' ')
current_offset = next_offset()
else:
print(" ", end=' ')
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, "--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()
# 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()
#! /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 re
import subprocess
import sys
__DESCRIPTION = """
Processes a perf.data sample file and reports the hottest Ignition bytecodes,
or write an input file for flamegraph.pl.
"""
__HELP_EPILOGUE = """
examples:
# Get a flamegraph for Ignition bytecode handlers on Octane benchmark,
# without considering the time spent compiling JS code, entry trampoline
# samples and other non-Ignition samples.
#
$ tools/run-perf.sh out/x64.release/d8 --noopt run.js
$ tools/ignition/linux_perf_report.py --flamegraph -o out.collapsed
$ flamegraph.pl --colors js out.collapsed > out.svg
# Same as above, but show all samples, including time spent compiling JS code,
# entry trampoline samples and other samples.
$ # ...
$ tools/ignition/linux_perf_report.py \\
--flamegraph --show-all -o out.collapsed
$ # ...
# Same as above, but show full function signatures in the flamegraph.
$ # ...
$ tools/ignition/linux_perf_report.py \\
--flamegraph --show-full-signatures -o out.collapsed
$ # ...
# See the hottest bytecodes on Octane benchmark, by number of samples.
#
$ tools/run-perf.sh out/x64.release/d8 --noopt octane/run.js
$ tools/ignition/linux_perf_report.py
"""
COMPILER_SYMBOLS_RE = re.compile(
r"v8::internal::(?:\(anonymous namespace\)::)?Compile|v8::internal::Parser")
JIT_CODE_SYMBOLS_RE = re.compile(
r"(LazyCompile|Compile|Eval|Script):(\*|~)")
GC_SYMBOLS_RE = re.compile(
r"v8::internal::Heap::CollectGarbage")
def strip_function_parameters(symbol):
if symbol[-1] != ')': return symbol
pos = 1
parenthesis_count = 0
for c in reversed(symbol):
if c == ')':
parenthesis_count += 1
elif c == '(':
parenthesis_count -= 1
if parenthesis_count == 0:
break
else:
pos += 1
return symbol[:-pos]
def collapsed_callchains_generator(perf_stream, hide_other=False,
hide_compiler=False, hide_jit=False,
hide_gc=False, show_full_signatures=False):
current_chain = []
skip_until_end_of_chain = False
compiler_symbol_in_chain = False
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:
if (not skip_until_end_of_chain and current_chain
and not hide_other):
current_chain.append("[other]")
yield current_chain
# Reset parser status.
current_chain = []
skip_until_end_of_chain = False
compiler_symbol_in_chain = False
continue
if skip_until_end_of_chain:
continue
# Trim the leading address and the trailing +offset, if present.
symbol = line.split(" ", 1)[1].split("+", 1)[0]
if not show_full_signatures:
symbol = strip_function_parameters(symbol)
# Avoid chains of [unknown]
if (symbol == "[unknown]" and current_chain and
current_chain[-1] == "[unknown]"):
continue
current_chain.append(symbol)
if symbol.startswith("BytecodeHandler:"):
current_chain.append("[interpreter]")
yield current_chain
skip_until_end_of_chain = True
elif JIT_CODE_SYMBOLS_RE.match(symbol):
if not hide_jit:
current_chain.append("[jit]")
yield current_chain
skip_until_end_of_chain = True
elif GC_SYMBOLS_RE.match(symbol):
if not hide_gc:
current_chain.append("[gc]")
yield current_chain
skip_until_end_of_chain = True
elif symbol == "Stub:CEntryStub" and compiler_symbol_in_chain:
if not hide_compiler:
current_chain.append("[compiler]")
yield current_chain
skip_until_end_of_chain = True
elif COMPILER_SYMBOLS_RE.match(symbol):
compiler_symbol_in_chain = True
elif symbol == "Builtin:InterpreterEntryTrampoline":
if len(current_chain) == 1:
yield ["[entry trampoline]"]
else:
# If we see an InterpreterEntryTrampoline which is not at the top of the
# chain and doesn't have a BytecodeHandler above it, then we have
# skipped the top BytecodeHandler due to the top-level stub not building
# a frame. File the chain in the [misattributed] bucket.
current_chain[-1] = "[misattributed]"
yield current_chain
skip_until_end_of_chain = True
def calculate_samples_count_per_callchain(callchains):
chain_counters = collections.defaultdict(int)
for callchain in callchains:
key = ";".join(reversed(callchain))
chain_counters[key] += 1
return chain_counters.items()
def calculate_samples_count_per_handler(callchains):
def strip_handler_prefix_if_any(handler):
return handler if handler[0] == "[" else handler.split(":", 1)[1]
handler_counters = collections.defaultdict(int)
for callchain in callchains:
handler = strip_handler_prefix_if_any(callchain[-1])
handler_counters[handler] += 1
return handler_counters.items()
def write_flamegraph_input_file(output_stream, callchains):
for callchain, count in calculate_samples_count_per_callchain(callchains):
output_stream.write("{}; {}\n".format(callchain, count))
def write_handlers_report(output_stream, callchains):
handler_counters = calculate_samples_count_per_handler(callchains)
samples_num = sum(counter for _, counter in handler_counters)
# Sort by decreasing number of samples
handler_counters.sort(key=lambda entry: entry[1], reverse=True)
for bytecode_name, count in handler_counters:
output_stream.write(
"{}\t{}\t{:.3f}%\n".format(bytecode_name, count,
100. * count / samples_num))
def parse_command_line():
command_line_parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__DESCRIPTION,
epilog=__HELP_EPILOGUE)
command_line_parser.add_argument(
"perf_filename",
help="perf sample file to process (default: perf.data)",
nargs="?",
default="perf.data",
metavar="<perf filename>"
)
command_line_parser.add_argument(
"--flamegraph", "-f",
help="output an input file for flamegraph.pl, not a report",
action="store_true",
dest="output_flamegraph"
)
command_line_parser.add_argument(
"--hide-other",
help="Hide other samples",
action="store_true"
)
command_line_parser.add_argument(
"--hide-compiler",
help="Hide samples during compilation",
action="store_true"
)
command_line_parser.add_argument(
"--hide-jit",
help="Hide samples from JIT code execution",
action="store_true"
)
command_line_parser.add_argument(
"--hide-gc",
help="Hide samples from garbage collection",
action="store_true"
)
command_line_parser.add_argument(
"--show-full-signatures", "-s",
help="show full signatures instead of function names",
action="store_true"
)
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"
)
return command_line_parser.parse_args()
def main():
program_options = parse_command_line()
perf = subprocess.Popen(["perf", "script", "--fields", "ip,sym",
"-i", program_options.perf_filename],
stdout=subprocess.PIPE)
callchains = collapsed_callchains_generator(
perf.stdout, program_options.hide_other, program_options.hide_compiler,
program_options.hide_jit, program_options.hide_gc,
program_options.show_full_signatures)
if program_options.output_flamegraph:
write_flamegraph_input_file(program_options.output_stream, callchains)
else:
write_handlers_report(program_options.output_stream, callchains)
if __name__ == "__main__":
main()
# 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 linux_perf_report as ipr
import StringIO
import unittest
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
76543210 baz
# Indentation shouldn't matter (neither should this line)
01234567 foo
abcdef76 BytecodeHandler:bar
76543210 baz
01234567 beep
abcdef76 BytecodeHandler:bar
76543210 baz
01234567 hello
abcdef76 v8::internal::Compiler
00000000 Stub:CEntryStub
76543210 world
11111111 BytecodeHandler:nope
00000000 Lost
11111111 Builtin:InterpreterEntryTrampoline
22222222 bar
00000000 hello
11111111 LazyCompile:~Foo
11111111 Builtin:InterpreterEntryTrampoline
22222222 bar
"""
class LinuxPerfReportTest(unittest.TestCase):
def test_collapsed_callchains_generator(self):
perf_stream = StringIO.StringIO(PERF_SCRIPT_OUTPUT)
callchains = list(ipr.collapsed_callchains_generator(perf_stream))
self.assertListEqual(callchains, [
['firstSymbol', 'secondSymbol', '[other]'],
["foo", "BytecodeHandler:bar", "[interpreter]"],
["foo", "BytecodeHandler:bar", "[interpreter]"],
["beep", "BytecodeHandler:bar", "[interpreter]"],
["hello", "v8::internal::Compiler", "Stub:CEntryStub", "[compiler]"],
["Lost", "[misattributed]"],
["hello", "LazyCompile:~Foo", "[jit]"],
["[entry trampoline]"],
])
def test_collapsed_callchains_generator_hide_other(self):
perf_stream = StringIO.StringIO(PERF_SCRIPT_OUTPUT)
callchains = list(ipr.collapsed_callchains_generator(perf_stream,
hide_other=True,
hide_compiler=True,
hide_jit=True))
self.assertListEqual(callchains, [
["foo", "BytecodeHandler:bar", "[interpreter]"],
["foo", "BytecodeHandler:bar", "[interpreter]"],
["beep", "BytecodeHandler:bar", "[interpreter]"],
["Lost", "[misattributed]"],
["[entry trampoline]"],
])
def test_calculate_samples_count_per_callchain(self):
counters = ipr.calculate_samples_count_per_callchain([
["foo", "BytecodeHandler:bar"],
["foo", "BytecodeHandler:bar"],
["beep", "BytecodeHandler:bar"],
["hello", "v8::internal::Compiler", "[compiler]"],
])
self.assertItemsEqual(counters, [
('BytecodeHandler:bar;foo', 2),
('BytecodeHandler:bar;beep', 1),
('[compiler];v8::internal::Compiler;hello', 1),
])
def test_calculate_samples_count_per_callchain(self):
counters = ipr.calculate_samples_count_per_callchain([
["foo", "BytecodeHandler:bar"],
["foo", "BytecodeHandler:bar"],
["beep", "BytecodeHandler:bar"],
])
self.assertItemsEqual(counters, [
('BytecodeHandler:bar;foo', 2),
('BytecodeHandler:bar;beep', 1),
])
def test_calculate_samples_count_per_handler_show_compile(self):
counters = ipr.calculate_samples_count_per_handler([
["foo", "BytecodeHandler:bar"],
["foo", "BytecodeHandler:bar"],
["beep", "BytecodeHandler:bar"],
["hello", "v8::internal::Compiler", "[compiler]"],
])
self.assertItemsEqual(counters, [
("bar", 3),
("[compiler]", 1)
])
def test_calculate_samples_count_per_handler_(self):
counters = ipr.calculate_samples_count_per_handler([
["foo", "BytecodeHandler:bar"],
["foo", "BytecodeHandler:bar"],
["beep", "BytecodeHandler:bar"],
])
self.assertItemsEqual(counters, [("bar", 3)])
def test_multiple_handlers(self):
perf_stream = StringIO.StringIO("""
0000 foo(bar)
1234 BytecodeHandler:first
5678 a::random::call<to>(something, else)
9abc BytecodeHandler:second
def0 otherIrrelevant(stuff)
1111 entrypoint
""")
callchains = list(ipr.collapsed_callchains_generator(perf_stream, False))
self.assertListEqual(callchains, [
["foo", "BytecodeHandler:first", "[interpreter]"],
])
def test_compiler_symbols_regex(self):
compiler_symbols = [
"v8::internal::Parser",
"v8::internal::(anonymous namespace)::Compile",
"v8::internal::Compiler::foo",
]
for compiler_symbol in compiler_symbols:
self.assertTrue(ipr.COMPILER_SYMBOLS_RE.match(compiler_symbol))
def test_jit_code_symbols_regex(self):
jit_code_symbols = [
"LazyCompile:~Foo blah.js",
"Eval:*",
"Script:*Bar tmp.js",
]
for jit_code_symbol in jit_code_symbols:
self.assertTrue(ipr.JIT_CODE_SYMBOLS_RE.match(jit_code_symbol))
def test_strip_function_parameters(self):
def should_match(signature, name):
self.assertEqual(ipr.strip_function_parameters(signature), name)
should_match("foo(bar)", "foo"),
should_match("Foo(foomatic::(anonymous)::bar(baz))", "Foo"),
should_match("v8::(anonymous ns)::bar<thing(with, parentheses)>(baz, poe)",
"v8::(anonymous ns)::bar<thing(with, parentheses)>")
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment