#!/usr/bin/env python # ------------------------------------------------------------------------ # Copyright IBM Corp. and others 2020 # # This program and the accompanying materials are made available under # the terms of the Eclipse Public License 2.0 which accompanies this # distribution and is available at https://www.eclipse.org/legal/epl-2.0/ # or the Apache License, Version 2.0 which accompanies this distribution and # is available at https://www.apache.org/licenses/LICENSE-2.0. # # This Source Code may also be made available under the following # Secondary Licenses when the conditions for such availability set # forth in the Eclipse Public License, v. 2.0 are satisfied: GNU # General Public License, version 2 with the GNU Classpath # Exception [1] and GNU General Public License, version 2 with the # OpenJDK Assembly Exception [2]. # # [1] https://www.gnu.org/software/classpath/license.html # [2] https://openjdk.org/legal/assembly-exception.html # # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0 # --------------------------------------------------------------------------- # # The performance profiling tool PERF generates data that can help to identify any performance issue in the Java JDKs. # However this data is kernel dependent, and is only readable from the machine where the PERF tool was run. # This script helps to parse the data that PERF generated on the user machine, and output the result in a text format, # which makes it easy to transfer and share. # # Usage: perf script | perf-hottest [=...] # attr: attributes that contain information about the compiler. Attributes include pc(program counter), # sym(function symbol), offs(offset within a funtion),so (shared object), thread (thread name) # Examples: # perf script | perf-hottest sym # perf script -G -F comm,tid,ip,sym,dso | perf-hottest sym # # This script checks to make sure the following conditions: # 1) the user has successfully taken perf.data, i.e. perf.data file exists # 2) the perf-.map file is located under /tmp. # 3) the version of Perf tool is up-to-date. Older Perf tool will output different content with different format, # which are not compatible with this script. from __future__ import print_function from collections import defaultdict, namedtuple import re, sys, textwrap, subprocess, os.path from os import listdir try: from builtins import hex as _builtin_hex except ImportError: from __builtin__ import hex as _builtin_hex Sample = namedtuple('Sample', 'pc sym offs so thread') def main(args): prerequisite_checks() if len(args) == 0: cmdline_error() if args[0] in ('-h', '--help'): if len(args) == 1: usage(out=sys.stdout, status=0) else: cmdline_error() attrs = set('pc sym offs so thread'.split()) group_by_attr = args[0] if group_by_attr not in attrs: cmdline_error() pred = lambda s: True def pred_compose(prev, attr, attr_re): return lambda s: (prev(s) and attr_re.search(str(getattr(s, attr)))) for arg in args[1:]: if '=' not in arg: printerr('error: filter argument not of the form =:', arg) cmdline_error() cur_filter_attr, cur_filter_re_src = arg.split('=', 1) try: cur_filter_re = re.compile(cur_filter_re_src) except re.error: printerr('error: invalid regular expression:', cur_filter_re_src) cmdline_error() pred = pred_compose(pred, cur_filter_attr, cur_filter_re) samples = (s for s in read_samples(sys.stdin) if pred(s)) counts = counts_by(group_by_attr, samples) total_count = sum(n for key, n in counts.iteritems()) assert (total_count == 0) == (len(counts) == 0) if len(counts) > 0: table = [] for key, n in sorted(counts.iteritems(), key=lambda pair: pair[1]): pct = 100.0 * float(n) / float(total_count) table.append((n, '{0:6.2f}%'.format(pct), key)) hex_key_fmt = '-0{width}x' key_fmt = '' if group_by_attr == 'pc': key_fmt = hex_key_fmt elif group_by_attr == 'offs' and '?' not in counts: key_fmt = hex_key_fmt format_table(('-{width}', '', key_fmt), table) print('', total_count, ' --total--') def cmdline_error(): usage(out=sys.stderr, status=1) def printerr(*args): print('error:', *args, out=sys.stderr) def usage(out, status): msg = textwrap.dedent('''\ usage: perf script | perf-hottest [=...] attr: attributes that contain information about the compiler. attr includes: pc (program counter), sym (function symbol), offs (offset within a function), so (shared object), thread (thread name) Example:perf script | /perf-hottest sym perf script -G -F comm,tid,ip,sym,dso | /perf-hottest sym ''') print(msg, file=out) sys.exit(status) def prerequisite_checks(): check_perf_version() check_perfdata_exist() check_perfmap_file_exist() def check_perf_version(): bashCommand = "perf --version" process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE) output, error = process.communicate() requiredMajorV = 3 requiredMinorV = 5 match = re.search(r'version (\d+)\.(\d+)\.', output) if match: majorV = int(match.group(1)) minorV = int(match.group(2)) print ("The Perf tool version is: ", majorV, ".",minorV) if majorV < requiredMajorV or (majorV == requiredMajorV and minorV < requiredMinorV): print ("The Perf tool installed is too old, please upgrade to at least ", requiredMajorV, ".", requiredMinorV) sys.exit(-1) def check_perfdata_exist(): perffile='perf.data' if os.path.exists(perffile): print ("The ", perffile, " file exists.") else: print ("The ", perffile, " doesn't exist, or has a different name perf-.data. Please update the file name to", perfile) sys.exit(-1) def check_perfmap_file_exist(): for filename in os.listdir('/tmp/'): match = re.search(r'perf-\d+\.map', filename) if match: print ("The map file exists.") return print ("The /tmp/perf-pid.map file doesn't exist.") sys.exit(-1) def read_samples(f): sample_re = r''' \A \s* (?P \S | \S .* \S ) # thread name \s+ (?: \d+ ) (?: \s+ | \s+\[\d+\]\s+) # space or something like " [000] " // core id \s* (?P [0-9a-fA-F]+ ) # program counter \s+ (?P \S | \S .+? \S ) # function symbol (?P \+ 0x [0-9a-fA-F]+ )? # offset within function \s+ \( (?P [^() \t]* ) # shared object \) \s* \Z ''' sample_re = re.compile(sample_re, re.VERBOSE) for line in f: m = sample_re.match(line) if m is not None: pc, sym, offs, so, thread = m.group('pc', 'sym', 'offs', 'so', 'thread') if sym == '[unknown]': sym = '[unknown:{0}]'.format(so) offs = int(offs, 16) if offs else '?' yield Sample(int(pc, 16), sym, offs, so, thread) def counts_by(by, samples): c = defaultdict(int) for s in samples: c[getattr(s, by)] += 1 return c def format_table(formats, rows): widths = [] for i, f in enumerate(formats): f = '{{0:{0}}}'.format(f.format(width='')) widths.append(max(len(f.format(row[i])) for row in rows)) row_fmt = ' ' + ' '.join( '{{{0}:{1}}}'.format(i, f.format(width=w)) for i, (f, w) in enumerate(zip(formats, widths))) for row in rows: print(row_fmt.format(*row)) if __name__ == '__main__': main(sys.argv[1:])