|  | #!/usr/bin/env python | 
|  | # | 
|  | # Licensed to the Apache Software Foundation (ASF) under one | 
|  | # or more contributor license agreements. See the NOTICE file | 
|  | # distributed with this work for additional information | 
|  | # regarding copyright ownership. The ASF licenses this file | 
|  | # to you under the Apache License, Version 2.0 (the | 
|  | # "License"); you may not use this file except in compliance | 
|  | # with the License. You may obtain a copy of the License at | 
|  | # | 
|  | #   http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # | 
|  | # Unless required by applicable law or agreed to in writing, | 
|  | # software distributed under the License is distributed on an | 
|  | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
|  | # KIND, either express or implied. See the License for the | 
|  | # specific language governing permissions and limitations | 
|  | # under the License. | 
|  | # | 
|  | """ | 
|  | This script can be used to make the output from | 
|  | apache::thrift::profile_print_info() more human-readable. | 
|  |  | 
|  | It translates each executable file name and address into the corresponding | 
|  | source file name, line number, and function name.  By default, it also | 
|  | demangles C++ symbol names. | 
|  | """ | 
|  |  | 
|  | import optparse | 
|  | import os | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  |  | 
|  |  | 
|  | class AddressInfo(object): | 
|  | """ | 
|  | A class to store information about a particular address in an object file. | 
|  | """ | 
|  | def __init__(self, obj_file, address): | 
|  | self.objectFile = obj_file | 
|  | self.address = address | 
|  | self.sourceFile = None | 
|  | self.sourceLine = None | 
|  | self.function = None | 
|  |  | 
|  |  | 
|  | g_addrs_by_filename = {} | 
|  |  | 
|  |  | 
|  | def get_address(filename, address): | 
|  | """ | 
|  | Retrieve an AddressInfo object for the specified object file and address. | 
|  |  | 
|  | Keeps a global list of AddressInfo objects.  Two calls to get_address() | 
|  | with the same filename and address will always return the same AddressInfo | 
|  | object. | 
|  | """ | 
|  | global g_addrs_by_filename | 
|  | try: | 
|  | by_address = g_addrs_by_filename[filename] | 
|  | except KeyError: | 
|  | by_address = {} | 
|  | g_addrs_by_filename[filename] = by_address | 
|  |  | 
|  | try: | 
|  | addr_info = by_address[address] | 
|  | except KeyError: | 
|  | addr_info = AddressInfo(filename, address) | 
|  | by_address[address] = addr_info | 
|  | return addr_info | 
|  |  | 
|  |  | 
|  | def translate_file_addresses(filename, addresses, options): | 
|  | """ | 
|  | Use addr2line to look up information for the specified addresses. | 
|  | All of the addresses must belong to the same object file. | 
|  | """ | 
|  | # Do nothing if we can't find the file | 
|  | if not os.path.isfile(filename): | 
|  | return | 
|  |  | 
|  | args = ['addr2line'] | 
|  | if options.printFunctions: | 
|  | args.append('-f') | 
|  | args.extend(['-e', filename]) | 
|  |  | 
|  | proc = subprocess.Popen(args, stdin=subprocess.PIPE, | 
|  | stdout=subprocess.PIPE) | 
|  | for address in addresses: | 
|  | assert address.objectFile == filename | 
|  | proc.stdin.write(address.address + '\n') | 
|  |  | 
|  | if options.printFunctions: | 
|  | function = proc.stdout.readline() | 
|  | function = function.strip() | 
|  | if not function: | 
|  | raise Exception('unexpected EOF from addr2line') | 
|  | address.function = function | 
|  |  | 
|  | file_and_line = proc.stdout.readline() | 
|  | file_and_line = file_and_line.strip() | 
|  | if not file_and_line: | 
|  | raise Exception('unexpected EOF from addr2line') | 
|  | idx = file_and_line.rfind(':') | 
|  | if idx < 0: | 
|  | msg = 'expected file and line number from addr2line; got %r' % \ | 
|  | (file_and_line,) | 
|  | msg += '\nfile=%r, address=%r' % (filename, address.address) | 
|  | raise Exception(msg) | 
|  |  | 
|  | address.sourceFile = file_and_line[:idx] | 
|  | address.sourceLine = file_and_line[idx + 1:] | 
|  |  | 
|  | (remaining_out, cmd_err) = proc.communicate() | 
|  | retcode = proc.wait() | 
|  | if retcode != 0: | 
|  | raise subprocess.CalledProcessError(retcode, args) | 
|  |  | 
|  |  | 
|  | def lookup_addresses(options): | 
|  | """ | 
|  | Look up source file information for all of the addresses currently stored | 
|  | in the global list of AddressInfo objects. | 
|  | """ | 
|  | global g_addrs_by_filename | 
|  | for (file, addresses) in g_addrs_by_filename.items(): | 
|  | translate_file_addresses(file, addresses.values(), options) | 
|  |  | 
|  |  | 
|  | class Entry(object): | 
|  | """ | 
|  | An entry in the thrift profile output. | 
|  | Contains a header line, and a backtrace. | 
|  | """ | 
|  | def __init__(self, header): | 
|  | self.header = header | 
|  | self.bt = [] | 
|  |  | 
|  | def addFrame(self, filename, address): | 
|  | # If libc was able to determine the symbols names, the filename | 
|  | # argument will be of the form <filename>(<function>+<offset>) | 
|  | # So, strip off anything after the last '(' | 
|  | idx = filename.rfind('(') | 
|  | if idx >= 0: | 
|  | filename = filename[:idx] | 
|  |  | 
|  | addr = get_address(filename, address) | 
|  | self.bt.append(addr) | 
|  |  | 
|  | def write(self, f, options): | 
|  | f.write(self.header) | 
|  | f.write('\n') | 
|  | n = 0 | 
|  | for address in self.bt: | 
|  | f.write('  #%-2d %s:%s\n' % (n, address.sourceFile, | 
|  | address.sourceLine)) | 
|  | n += 1 | 
|  | if options.printFunctions: | 
|  | if address.function: | 
|  | f.write('      %s\n' % (address.function,)) | 
|  | else: | 
|  | f.write('      ??\n') | 
|  |  | 
|  |  | 
|  | def process_file(in_file, out_file, options): | 
|  | """ | 
|  | Read thrift profile output from the specified input file, and print | 
|  | prettier information on the output file. | 
|  | """ | 
|  | # | 
|  | # A naive approach would be to read the input line by line, | 
|  | # and each time we come to a filename and address, pass it to addr2line | 
|  | # and print the resulting information.  Unfortunately, addr2line can be | 
|  | # quite slow, especially with large executables. | 
|  | # | 
|  | # This approach is much faster.  We read in all of the input, storing | 
|  | # the addresses in each file that need to be resolved.  We then call | 
|  | # addr2line just once for each file.  This is much faster than calling | 
|  | # addr2line once per address. | 
|  | # | 
|  |  | 
|  | virt_call_regex = re.compile(r'^\s*T_VIRTUAL_CALL: (\d+) calls on (.*):$') | 
|  | gen_prot_regex = re.compile( | 
|  | r'^\s*T_GENERIC_PROTOCOL: (\d+) calls to (.*) with a (.*):$') | 
|  | bt_regex = re.compile(r'^\s*#(\d+)\s*(.*) \[(0x[0-9A-Za-z]+)\]$') | 
|  |  | 
|  | # Parse all of the input, and store it as Entry objects | 
|  | entries = [] | 
|  | current_entry = None | 
|  | while True: | 
|  | line = in_file.readline() | 
|  | if not line: | 
|  | break | 
|  |  | 
|  | if line == '\n' or line.startswith('Thrift virtual call info:'): | 
|  | continue | 
|  |  | 
|  | virt_call_match = virt_call_regex.match(line) | 
|  | if virt_call_match: | 
|  | num_calls = int(virt_call_match.group(1)) | 
|  | type_name = virt_call_match.group(2) | 
|  | if options.cxxfilt: | 
|  | # Type names reported by typeid() are internal names. | 
|  | # By default, c++filt doesn't demangle internal type names. | 
|  | # (Some versions of c++filt have a "-t" option to enable this. | 
|  | # Other versions don't have this argument, but demangle type | 
|  | # names passed as an argument, but not on stdin.) | 
|  | # | 
|  | # If the output is being filtered through c++filt, prepend | 
|  | # "_Z" to the type name to make it look like an external name. | 
|  | type_name = '_Z' + type_name | 
|  | header = 'T_VIRTUAL_CALL: %d calls on "%s"' % \ | 
|  | (num_calls, type_name) | 
|  | if current_entry is not None: | 
|  | entries.append(current_entry) | 
|  | current_entry = Entry(header) | 
|  | continue | 
|  |  | 
|  | gen_prot_match = gen_prot_regex.match(line) | 
|  | if gen_prot_match: | 
|  | num_calls = int(gen_prot_match.group(1)) | 
|  | type_name1 = gen_prot_match.group(2) | 
|  | type_name2 = gen_prot_match.group(3) | 
|  | if options.cxxfilt: | 
|  | type_name1 = '_Z' + type_name1 | 
|  | type_name2 = '_Z' + type_name2 | 
|  | header = 'T_GENERIC_PROTOCOL: %d calls to "%s" with a "%s"' % \ | 
|  | (num_calls, type_name1, type_name2) | 
|  | if current_entry is not None: | 
|  | entries.append(current_entry) | 
|  | current_entry = Entry(header) | 
|  | continue | 
|  |  | 
|  | bt_match = bt_regex.match(line) | 
|  | if bt_match: | 
|  | if current_entry is None: | 
|  | raise Exception('found backtrace frame before entry header') | 
|  | frame_num = int(bt_match.group(1)) | 
|  | filename = bt_match.group(2) | 
|  | address = bt_match.group(3) | 
|  | current_entry.addFrame(filename, address) | 
|  | continue | 
|  |  | 
|  | raise Exception('unexpected line in input: %r' % (line,)) | 
|  |  | 
|  | # Add the last entry we were processing to the list | 
|  | if current_entry is not None: | 
|  | entries.append(current_entry) | 
|  | current_entry = None | 
|  |  | 
|  | # Look up all of the addresses | 
|  | lookup_addresses(options) | 
|  |  | 
|  | # Print out the entries, now that the information has been translated | 
|  | for entry in entries: | 
|  | entry.write(out_file, options) | 
|  | out_file.write('\n') | 
|  |  | 
|  |  | 
|  | def start_cppfilt(): | 
|  | (read_pipe, write_pipe) = os.pipe() | 
|  |  | 
|  | # Fork.  Run c++filt in the parent process, | 
|  | # and then continue normal processing in the child. | 
|  | pid = os.fork() | 
|  | if pid == 0: | 
|  | # child | 
|  | os.dup2(write_pipe, sys.stdout.fileno()) | 
|  | os.close(read_pipe) | 
|  | os.close(write_pipe) | 
|  | return | 
|  | else: | 
|  | # parent | 
|  | os.dup2(read_pipe, sys.stdin.fileno()) | 
|  | os.close(read_pipe) | 
|  | os.close(write_pipe) | 
|  |  | 
|  | cmd = ['c++filt'] | 
|  | os.execvp(cmd[0], cmd) | 
|  |  | 
|  |  | 
|  | def main(argv): | 
|  | parser = optparse.OptionParser(usage='%prog [options] [<file>]') | 
|  | parser.add_option('--no-functions', help='Don\'t print function names', | 
|  | dest='printFunctions', action='store_false', | 
|  | default=True) | 
|  | parser.add_option('--no-demangle', | 
|  | help='Don\'t demangle C++ symbol names', | 
|  | dest='cxxfilt', action='store_false', | 
|  | default=True) | 
|  |  | 
|  | (options, args) = parser.parse_args(argv[1:]) | 
|  | num_args = len(args) | 
|  | if num_args == 0: | 
|  | in_file = sys.stdin | 
|  | elif num_args == 1: | 
|  | in_file = open(argv[1], 'r') | 
|  | else: | 
|  | parser.print_usage(sys.stderr) | 
|  | print >> sys.stderr, 'trailing arguments: %s' % (' '.join(args[1:],)) | 
|  | return 1 | 
|  |  | 
|  | if options.cxxfilt: | 
|  | start_cppfilt() | 
|  |  | 
|  | process_file(in_file, sys.stdout, options) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | rc = main(sys.argv) | 
|  | sys.exit(rc) |