summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2017-11-01 14:46:32 -0700
committerYabin Cui <yabinc@google.com>2017-11-01 15:47:19 -0700
commitb956d24108cab878b639e4d6ebd04e80c80ce780 (patch)
tree88948d61af688335a11ec756a7cf5ebf01142563
parent29c0043b5334432afc01584f435585c2d2da49fe (diff)
downloadextras-b956d24108cab878b639e4d6ebd04e80c80ce780.tar.gz
simpleperf: support disassembly in html report interface.
Bug: http://b/66914187 Test: run test.py. Change-Id: Ib5f027c7236e0d5348991abcd82021faf8a84426
-rw-r--r--simpleperf/scripts/report_html.js114
-rw-r--r--simpleperf/scripts/report_html.py69
-rw-r--r--simpleperf/scripts/test.py1
3 files changed, 166 insertions, 18 deletions
diff --git a/simpleperf/scripts/report_html.js b/simpleperf/scripts/report_html.js
index 6f6b0210..ac79568b 100644
--- a/simpleperf/scripts/report_html.js
+++ b/simpleperf/scripts/report_html.js
@@ -88,6 +88,11 @@ function getFuncSourceRange(funcId) {
return null;
}
+function getFuncDisassembly(funcId) {
+ let func = gFunctionMap[funcId];
+ return func.hasOwnProperty('d') ? func.d : null;
+}
+
function getSourceFilePath(sourceFileId) {
return gSourceFiles[sourceFileId].path;
}
@@ -459,6 +464,7 @@ class FunctionTab {
this.callgraphView = null;
this.reverseCallgraphView = null;
this.sourceCodeView = null;
+ this.disassemblyView = null;
this.draw();
gTabs.setActive(this);
}
@@ -499,6 +505,13 @@ class FunctionTab {
this.sourceCodeView = new SourceCodeView(this.div, sourceFiles);
}
+ let disassembly = collectDisassemblyForFunction(this.func);
+ if (disassembly) {
+ this.div.append(getHtml('hr'));
+ this.div.append(getHtml('b', {text: 'Disassembly:'}) + '<br/>');
+ this.disassemblyView = new DisassemblyView(this.div, disassembly);
+ }
+
this.onSampleWeightChange(); // Manually set sample weight function for the first time.
}
@@ -513,6 +526,9 @@ class FunctionTab {
if (this.sourceCodeView) {
this.sourceCodeView.draw(sampleWeightFunction);
}
+ if (this.disassemblyView) {
+ this.disassemblyView.draw(sampleWeightFunction);
+ }
}
}
@@ -911,10 +927,10 @@ class SourceFile {
// Return a list of SourceFile related to a function.
function collectSourceFilesForFunction(func) {
- if (!func.hasOwnProperty('sc')) {
+ if (!func.hasOwnProperty('s')) {
return null;
}
- let hitLines = func.sc;
+ let hitLines = func.s;
let sourceFiles = {}; // map from sourceFileId to SourceFile.
function getFile(fileId) {
@@ -1008,13 +1024,105 @@ class SourceCodeView {
table.draw(data, {
width: '100%',
sort: 'disable',
- fronzenColumns: 3,
+ frozenColumns: 3,
allowHtml: true,
});
}
}
}
+// Return a list of disassembly related to a function.
+function collectDisassemblyForFunction(func) {
+ if (!func.hasOwnProperty('a')) {
+ return null;
+ }
+ let hitAddrs = func.a;
+ let rawCode = getFuncDisassembly(func.g.f);
+ if (!rawCode) {
+ return null;
+ }
+
+ // Annotate disassembly with event count information.
+ let annotatedCode = [];
+ let codeForLastAddr = null;
+ let hitAddrPos = 0;
+ let hasCount = false;
+
+ function addEventCount(addr) {
+ while (hitAddrPos < hitAddrs.length && hitAddrs[hitAddrPos].a < addr) {
+ if (codeForLastAddr) {
+ codeForLastAddr.eventCount += hitAddrs[hitAddrPos].e;
+ codeForLastAddr.subtreeEventCount += hitAddrs[hitAddrPos].s;
+ hasCount = true;
+ }
+ hitAddrPos++;
+ }
+ }
+
+ for (let line of rawCode) {
+ let code = line[0];
+ let addr = line[1];
+
+ addEventCount(addr);
+ let item = {code: code, eventCount: 0, subtreeEventCount: 0};
+ annotatedCode.push(item);
+ // Objdump sets addr to 0 when a disassembly line is not associated with an addr.
+ if (addr != 0) {
+ codeForLastAddr = item;
+ }
+ }
+ addEventCount(Number.MAX_VALUE);
+ return hasCount ? annotatedCode : null;
+}
+
+// Show annotated disassembly of a function.
+class DisassemblyView {
+
+ constructor(divContainer, disassembly) {
+ this.div = $('<div>');
+ this.div.appendTo(divContainer);
+ this.disassembly = disassembly;
+ }
+
+ draw(sampleWeightFunction) {
+ google.charts.setOnLoadCallback(() => this.realDraw(sampleWeightFunction));
+ }
+
+ realDraw(sampleWeightFunction) {
+ this.div.empty();
+ // Draw a table of 'Total', 'Self', 'Code'.
+ let rows = [];
+ for (let line of this.disassembly) {
+ let code = getHtml('pre', {text: line.code});
+ let totalValue = '';
+ let selfValue = '';
+ if (line.subtreeEventCount != 0) {
+ totalValue = sampleWeightFunction(line.subtreeEventCount);
+ selfValue = sampleWeightFunction(line.eventCount);
+ }
+ rows.push([totalValue, selfValue, line.code]);
+ }
+ let data = new google.visualization.DataTable();
+ data.addColumn('string', 'Total');
+ data.addColumn('string', 'Self');
+ data.addColumn('string', 'Code');
+ data.addRows(rows);
+ for (let i = 0; i < this.disassembly.length; ++i) {
+ for (let j = 0; j < 2; ++j) {
+ data.setProperty(i, j, 'className', 'colForCount');
+ }
+ }
+ let wrapperDiv = $('<div>');
+ wrapperDiv.appendTo(this.div);
+ let table = new google.visualization.Table(wrapperDiv.get(0));
+ table.draw(data, {
+ width: '100%',
+ sort: 'disable',
+ frozenColumns: 2,
+ allowHtml: true,
+ });
+ }
+}
function initGlobalObjects() {
diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py
index d06cd10e..28302e53 100644
--- a/simpleperf/scripts/report_html.py
+++ b/simpleperf/scripts/report_html.py
@@ -74,11 +74,12 @@ class EventScope(object):
process = self.processes[pid] = ProcessScope(pid)
return process
- def get_sample_info(self):
+ def get_sample_info(self, gen_addr_hit_map):
result = {}
result['eventName'] = self.name
result['eventCount'] = self.event_count
- result['processes'] = [process.get_sample_info() for process in self.processes.values()]
+ result['processes'] = [process.get_sample_info(gen_addr_hit_map)
+ for process in self.processes.values()]
return result
@@ -99,11 +100,12 @@ class ProcessScope(object):
self.name = thread_name
return thread
- def get_sample_info(self):
+ def get_sample_info(self, gen_addr_hit_map):
result = {}
result['pid'] = self.pid
result['eventCount'] = self.event_count
- result['threads'] = [thread.get_sample_info() for thread in self.threads.values()]
+ result['threads'] = [thread.get_sample_info(gen_addr_hit_map)
+ for thread in self.threads.values()]
return result
@@ -148,11 +150,12 @@ class ThreadScope(object):
lib = self.libs.get(lib_id)
lib.get_function(func_id).add_callchain(callstack, i - 1, -1, event_count)
- def get_sample_info(self):
+ def get_sample_info(self, gen_addr_hit_map):
result = {}
result['tid'] = self.tid
result['eventCount'] = self.event_count
- result['libs'] = [lib.gen_sample_info() for lib in self.libs.values()]
+ result['libs'] = [lib.gen_sample_info(gen_addr_hit_map)
+ for lib in self.libs.values()]
return result
@@ -169,11 +172,12 @@ class LibScope(object):
function = self.functions[func_id] = FunctionScope(func_id)
return function
- def gen_sample_info(self):
+ def gen_sample_info(self, gen_addr_hit_map):
result = {}
result['libId'] = self.lib_id
result['eventCount'] = self.event_count
- result['functions'] = [func.gen_sample_info() for func in self.functions.values()]
+ result['functions'] = [func.gen_sample_info(gen_addr_hit_map)
+ for func in self.functions.values()]
return result
@@ -230,7 +234,7 @@ class FunctionScope(object):
self.call_graph.cut_edge(min_limit, hit_func_ids)
self.reverse_call_graph.cut_edge(min_limit, hit_func_ids)
- def gen_sample_info(self):
+ def gen_sample_info(self, gen_addr_hit_map):
result = {}
result['c'] = self.sample_count
result['g'] = self.call_graph.gen_sample_info()
@@ -241,7 +245,13 @@ class FunctionScope(object):
count_info = self.line_hit_map[key]
item = {'f': key[0], 'l': key[1], 'e': count_info[0], 's': count_info[1]}
items.append(item)
- result['sc'] = items
+ result['s'] = items
+ if gen_addr_hit_map and self.addr_hit_map:
+ items = []
+ for addr in sorted(self.addr_hit_map):
+ count_info = self.addr_hit_map[addr]
+ items.append({'a': addr, 'e': count_info[0], 's': count_info[1]})
+ result['a'] = items
return result
@@ -313,6 +323,7 @@ class Function(object):
self.start_addr = start_addr
self.addr_len = addr_len
self.source_info = None
+ self.disassembly = None
class FunctionSet(object):
@@ -465,6 +476,7 @@ class RecordData(object):
l: libId
f: functionName
s: [sourceFileId, startLine, endLine] [optional]
+ d: [(disassembly, addr)] [optional]
}
10. sampleInfo = [eventInfo]
@@ -492,7 +504,8 @@ class RecordData(object):
c: sampleCount
g: callGraph
rg: reverseCallgraph
- sc: [sourceCodeInfo] [optional]
+ s: [sourceCodeInfo] [optional]
+ a: [addrInfo] (sorted by addrInfo.addr) [optional]
}
callGraph and reverseCallGraph are both of type CallNode.
callGraph shows how a function calls other functions.
@@ -511,6 +524,12 @@ class RecordData(object):
s: subtreeEventCount
}
+ addrInfo {
+ a: addr
+ e: eventCount
+ s: subtreeEventCount
+ }
+
11. sourceFiles: an array of sourceFile, indexed by sourceFileId.
sourceFile {
path
@@ -530,6 +549,7 @@ class RecordData(object):
self.functions = FunctionSet()
self.total_samples = 0
self.source_files = SourceFileSet()
+ self.gen_addr_hit_map_in_record_info = False
def load_record_file(self, record_file):
lib = ReportLib()
@@ -662,6 +682,19 @@ class RecordData(object):
# Collect needed source code in SourceFileSet.
self.source_files.load_source_code(source_dirs)
+ def add_disassembly(self):
+ """ Collect disassembly information:
+ 1. Use objdump to collect disassembly for each function in FunctionSet.
+ 2. Set flag to dump addr_hit_map when generating record info.
+ """
+ objdump = Objdump(self.ndk_path, self.binary_cache_path)
+ for function in self.functions.id_to_func.values():
+ lib_name = self.libs.get_lib_name(function.lib_id)
+ code = objdump.disassemble_code(lib_name, function.start_addr, function.addr_len)
+ function.disassembly = code
+
+ self.gen_addr_hit_map_in_record_info = True
+
def gen_record_info(self):
record_info = {}
timestamp = self.meta_info.get('timestamp')
@@ -718,11 +751,14 @@ class RecordData(object):
func_data['f'] = self._modify_name_for_html(function.func_name)
if function.source_info:
func_data['s'] = function.source_info
+ if function.disassembly:
+ func_data['d'] = function.disassembly
func_map[func_id] = func_data
return func_map
def _gen_sample_info(self):
- return [event.get_sample_info() for event in self.events.values()]
+ return [event.get_sample_info(self.gen_addr_hit_map_in_record_info)
+ for event in self.events.values()]
def _gen_source_files(self):
source_files = sorted(self.source_files.path_to_source_files.values(),
@@ -820,6 +856,7 @@ def main():
the starting function are collected in the report. Default is 0.01.""")
parser.add_argument('--add_source_code', action='store_true', help='Add source code.')
parser.add_argument('--source_dirs', nargs='+', help='Source code directories.')
+ parser.add_argument('--add_disassembly', action='store_true', help='Add disassembled code.')
parser.add_argument('--ndk_path', nargs=1, help='Find tools in the ndk path.')
parser.add_argument('--no_browser', action='store_true', help="Don't open report in browser.")
args = parser.parse_args()
@@ -827,8 +864,8 @@ def main():
# 1. Process args.
binary_cache_path = 'binary_cache'
if not os.path.isdir(binary_cache_path):
- if args.add_source_code:
- log_exit("""binary_cache/ doesn't exist. Can't add source code or disassemble code
+ if args.add_source_code or args.add_disassembly:
+ log_exit("""binary_cache/ doesn't exist. Can't add source code or disassembled code
without collected binaries. Please run binary_cache_builder.py to
collect binaries for current profiling data, or run app_profiler.py
without -nb option.""")
@@ -836,7 +873,7 @@ def main():
if args.add_source_code and not args.source_dirs:
log_exit('--source_dirs is needed to add source code.')
- build_addr_hit_map = args.add_source_code
+ build_addr_hit_map = args.add_source_code or args.add_disassembly
ndk_path = None if not args.ndk_path else args.ndk_path[0]
# 2. Produce record data.
@@ -846,6 +883,8 @@ def main():
record_data.limit_percents(args.min_func_percent, args.min_callchain_percent)
if args.add_source_code:
record_data.add_source_code(args.source_dirs)
+ if args.add_disassembly:
+ record_data.add_disassembly()
# 3. Generate report html.
report_generator = ReportGenerator(args.report_path)
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
index d4ff5125..962bf1c1 100644
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -326,6 +326,7 @@ class TestExampleBase(TestBase):
self.run_app_profiler()
self.run_cmd(['report_html.py'])
self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata'])
+ self.run_cmd(['report_html.py', '--add_disassembly'])
# Test with multiple perf.data.
shutil.move('perf.data', 'perf2.data')
self.run_app_profiler()