| #!/usr/bin/python
|
|
|
| #
|
| # Copyright (c) 2014, ARM Limited. All rights reserved.
|
| #
|
| # SPDX-License-Identifier: BSD-2-Clause-Patent
|
| #
|
|
|
| import getopt
|
| import operator
|
| import os
|
| import pickle
|
| import sys
|
| from sys import argv
|
| from cStringIO import StringIO
|
|
|
| modules = {}
|
| functions = {}
|
| functions_addr = {}
|
|
|
| def usage():
|
| print "-t,--trace: Location of the Trace file"
|
| print "-s,--symbols: Location of the symbols and modules"
|
|
|
| def get_address_from_string(address):
|
| return int(address.strip("S:").strip("N:").strip("EL2:").strip("EL1:"), 16)
|
|
|
| def get_module_from_addr(modules, addr):
|
| for key,value in modules.items():
|
| if (value['start'] <= addr) and (addr <= value['end']):
|
| return key
|
| return None
|
|
|
| def add_cycles_to_function(functions, func_name, addr, cycles):
|
| if func_name != "<Unknown>":
|
| # Check if we are still in the previous function
|
| if add_cycles_to_function.prev_func_name == func_name:
|
| add_cycles_to_function.prev_entry['cycles'] += cycles
|
| return (add_cycles_to_function.prev_func_name, add_cycles_to_function.prev_module_name)
|
|
|
| if func_name in functions.keys():
|
| for module_name, module_value in functions[func_name].iteritems():
|
| if (module_value['start'] <= addr) and (addr < module_value['end']):
|
| module_value['cycles'] += cycles
|
|
|
| add_cycles_to_function.prev_func_name = func_name
|
| add_cycles_to_function.prev_module_name = module_name
|
| add_cycles_to_function.prev_entry = module_value
|
| return (func_name, module_name)
|
| elif (module_value['end'] == 0):
|
| module_value['cycles'] += cycles
|
|
|
| add_cycles_to_function.prev_func_name = func_name
|
| add_cycles_to_function.prev_module_name = module_name
|
| add_cycles_to_function.prev_entry = module_value
|
| return (func_name, module_name)
|
|
|
| # Workaround to fix the 'info func' limitation that does not expose the 'static' function
|
| module_name = get_module_from_addr(modules, addr)
|
| functions[func_name] = {}
|
| functions[func_name][module_name] = {}
|
| functions[func_name][module_name]['start'] = 0
|
| functions[func_name][module_name]['end'] = 0
|
| functions[func_name][module_name]['cycles'] = cycles
|
| functions[func_name][module_name]['count'] = 0
|
|
|
| add_cycles_to_function.prev_func_name = func_name
|
| add_cycles_to_function.prev_module_name = module_name
|
| add_cycles_to_function.prev_entry = functions[func_name][module_name]
|
| return (func_name, module_name)
|
| else:
|
| # Check if we are still in the previous function
|
| if (add_cycles_to_function.prev_entry is not None) and (add_cycles_to_function.prev_entry['start'] <= addr) and (addr < add_cycles_to_function.prev_entry['end']):
|
| add_cycles_to_function.prev_entry['cycles'] += cycles
|
| return (add_cycles_to_function.prev_func_name, add_cycles_to_function.prev_module_name)
|
|
|
| # Generate the key for the given address
|
| key = addr & ~0x0FFF
|
|
|
| if key not in functions_addr.keys():
|
| if 'Unknown' not in functions.keys():
|
| functions['Unknown'] = {}
|
| if 'Unknown' not in functions['Unknown'].keys():
|
| functions['Unknown']['Unknown'] = {}
|
| functions['Unknown']['Unknown']['cycles'] = 0
|
| functions['Unknown']['Unknown']['count'] = 0
|
| functions['Unknown']['Unknown']['cycles'] += cycles
|
|
|
| add_cycles_to_function.prev_func_name = None
|
| return None
|
|
|
| for func_key, module in functions_addr[key].iteritems():
|
| for module_key, module_value in module.iteritems():
|
| if (module_value['start'] <= addr) and (addr < module_value['end']):
|
| module_value['cycles'] += cycles
|
|
|
| # In case o <Unknown> we prefer to fallback on the direct search
|
| add_cycles_to_function.prev_func_name = func_key
|
| add_cycles_to_function.prev_module_name = module_key
|
| add_cycles_to_function.prev_entry = module_value
|
| return (func_key, module_key)
|
|
|
| print "Warning: Function %s @ 0x%x not found" % (func_name, addr)
|
|
|
| add_cycles_to_function.prev_func_name = None
|
| return None
|
|
|
| # Static variables for the previous function
|
| add_cycles_to_function.prev_func_name = None
|
| add_cycles_to_function.prev_entry = None
|
|
|
| def trace_read():
|
| global trace_process
|
| line = trace.readline()
|
| trace_process += len(line)
|
| return line
|
|
|
| #
|
| # Parse arguments
|
| #
|
| trace_name = None
|
| symbols_file = None
|
|
|
| opts,args = getopt.getopt(sys.argv[1:], "ht:vs:v", ["help","trace=","symbols="])
|
| if (opts is None) or (not opts):
|
| usage()
|
| sys.exit()
|
|
|
| for o,a in opts:
|
| if o in ("-h","--help"):
|
| usage()
|
| sys.exit()
|
| elif o in ("-t","--trace"):
|
| trace_name = a
|
| elif o in ("-s","--symbols"):
|
| symbols_file = a
|
| else:
|
| assert False, "Unhandled option (%s)" % o
|
|
|
| #
|
| # We try first to see if we run the script from DS-5
|
| #
|
| try:
|
| from arm_ds.debugger_v1 import Debugger
|
| from arm_ds.debugger_v1 import DebugException
|
|
|
| # Debugger object for accessing the debugger
|
| debugger = Debugger()
|
|
|
| # Initialisation commands
|
| ec = debugger.getExecutionContext(0)
|
| ec.getExecutionService().stop()
|
| ec.getExecutionService().waitForStop()
|
| # in case the execution context reference is out of date
|
| ec = debugger.getExecutionContext(0)
|
|
|
| #
|
| # Get the module name and their memory range
|
| #
|
| info_file = ec.executeDSCommand("info file")
|
| info_file_str = StringIO(info_file)
|
|
|
| line = info_file_str.readline().strip('\n')
|
| while line != '':
|
| if ("Symbols from" in line):
|
| # Get the module name from the line 'Symbols from "/home/...."'
|
| module_name = line.split("\"")[1].split("/")[-1]
|
| modules[module_name] = {}
|
|
|
| # Look for the text section
|
| line = info_file_str.readline().strip('\n')
|
| while (line != '') and ("Symbols from" not in line):
|
| if ("ER_RO" in line):
|
| modules[module_name]['start'] = get_address_from_string(line.split()[0])
|
| modules[module_name]['end'] = get_address_from_string(line.split()[2])
|
| line = info_file_str.readline().strip('\n')
|
| break;
|
| if (".text" in line):
|
| modules[module_name]['start'] = get_address_from_string(line.split()[0])
|
| modules[module_name]['end'] = get_address_from_string(line.split()[2])
|
| line = info_file_str.readline().strip('\n')
|
| break;
|
| line = info_file_str.readline().strip('\n')
|
| line = info_file_str.readline().strip('\n')
|
|
|
| #
|
| # Get the function name and their memory range
|
| #
|
| info_func = ec.executeDSCommand("info func")
|
| info_func_str = StringIO(info_func)
|
|
|
| # Skip the first line 'Low-level symbols ...'
|
| line = info_func_str.readline().strip('\n')
|
| func_prev = None
|
| while line != '':
|
| # We ignore all the functions after 'Functions in'
|
| if ("Functions in " in line):
|
| line = info_func_str.readline().strip('\n')
|
| while line != '':
|
| line = info_func_str.readline().strip('\n')
|
| line = info_func_str.readline().strip('\n')
|
| continue
|
|
|
| if ("Low-level symbols" in line):
|
| # We need to fixup the last function of the module
|
| if func_prev is not None:
|
| func_prev['end'] = modules[module_name]['end']
|
| func_prev = None
|
|
|
| line = info_func_str.readline().strip('\n')
|
| continue
|
|
|
| func_name = line.split()[1]
|
| func_start = get_address_from_string(line.split()[0])
|
| module_name = get_module_from_addr(modules, func_start)
|
|
|
| if func_name not in functions.keys():
|
| functions[func_name] = {}
|
| functions[func_name][module_name] = {}
|
| functions[func_name][module_name]['start'] = func_start
|
| functions[func_name][module_name]['cycles'] = 0
|
| functions[func_name][module_name]['count'] = 0
|
|
|
| # Set the end address of the previous function
|
| if func_prev is not None:
|
| func_prev['end'] = func_start
|
| func_prev = functions[func_name][module_name]
|
|
|
| line = info_func_str.readline().strip('\n')
|
|
|
| # Fixup the last function
|
| func_prev['end'] = modules[module_name]['end']
|
|
|
| if symbols_file is not None:
|
| pickle.dump((modules, functions), open(symbols_file, "w"))
|
| except:
|
| if symbols_file is None:
|
| print "Error: Symbols file is required when run out of ARM DS-5"
|
| sys.exit()
|
|
|
| (modules, functions) = pickle.load(open(symbols_file, "r"))
|
|
|
| #
|
| # Build optimized table for the <Unknown> functions
|
| #
|
| functions_addr = {}
|
| for func_key, module in functions.iteritems():
|
| for module_key, module_value in module.iteritems():
|
| key = module_value['start'] & ~0x0FFF
|
| if key not in functions_addr.keys():
|
| functions_addr[key] = {}
|
| if func_key not in functions_addr[key].keys():
|
| functions_addr[key][func_key] = {}
|
| functions_addr[key][func_key][module_key] = module_value
|
|
|
| #
|
| # Process the trace file
|
| #
|
| if trace_name is None:
|
| sys.exit()
|
|
|
| trace = open(trace_name, "r")
|
| trace_size = os.path.getsize(trace_name)
|
| trace_process = 0
|
|
|
| # Get the column names from the first line
|
| columns = trace_read().split()
|
| column_addr = columns.index('Address')
|
| column_cycles = columns.index('Cycles')
|
| column_function = columns.index('Function')
|
|
|
| line = trace_read()
|
| i = 0
|
| prev_callee = None
|
| while line:
|
| try:
|
| func_name = line.split('\t')[column_function].strip()
|
| address = get_address_from_string(line.split('\t')[column_addr])
|
| cycles = int(line.split('\t')[column_cycles])
|
| callee = add_cycles_to_function(functions, func_name, address, cycles)
|
| if (prev_callee != None) and (prev_callee != callee):
|
| functions[prev_callee[0]][prev_callee[1]]['count'] += 1
|
| prev_callee = callee
|
| except ValueError:
|
| pass
|
| line = trace_read()
|
| if ((i % 1000000) == 0) and (i != 0):
|
| percent = (trace_process * 100.00) / trace_size
|
| print "Processing file ... (%.2f %%)" % (percent)
|
| i = i + 1
|
|
|
| # Fixup the last callee
|
| functions[prev_callee[0]][prev_callee[1]]['count'] += 1
|
|
|
| #
|
| # Process results
|
| #
|
| functions_cycles = {}
|
| all_functions_cycles = {}
|
| total_cycles = 0
|
|
|
| for func_key, module in functions.iteritems():
|
| for module_key, module_value in module.iteritems():
|
| key = "%s/%s" % (module_key, func_key)
|
| functions_cycles[key] = (module_value['cycles'], module_value['count'])
|
| total_cycles += module_value['cycles']
|
|
|
| if func_key not in all_functions_cycles.keys():
|
| all_functions_cycles[func_key] = (module_value['cycles'], module_value['count'])
|
| else:
|
| all_functions_cycles[func_key] = tuple(map(sum, zip(all_functions_cycles[func_key], (module_value['cycles'], module_value['count']))))
|
|
|
| sorted_functions_cycles = sorted(functions_cycles.iteritems(), key=operator.itemgetter(1), reverse = True)
|
| sorted_all_functions_cycles = sorted(all_functions_cycles.items(), key=operator.itemgetter(1), reverse = True)
|
|
|
| print
|
| print "----"
|
| for (key,value) in sorted_functions_cycles[:20]:
|
| if value[0] != 0:
|
| print "%s (cycles: %d - %d%%, count: %d)" % (key, value[0], (value[0] * 100) / total_cycles, value[1])
|
| else:
|
| break;
|
| print "----"
|
| for (key,value) in sorted_all_functions_cycles[:20]:
|
| if value[0] != 0:
|
| print "%s (cycles: %d - %d%%, count: %d)" % (key, value[0], (value[0] * 100) / total_cycles, value[1])
|
| else:
|
| break;
|