| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library code_view_element; |
| |
| import 'dart:async'; |
| import 'dart:html'; |
| import 'observatory_element.dart'; |
| import 'package:observatory/app.dart'; |
| import 'package:observatory/service.dart'; |
| import 'package:observatory/cpu_profile.dart'; |
| import 'package:polymer/polymer.dart'; |
| |
| class DisassemblyTable extends SortedTable { |
| DisassemblyTable(columns) : super(columns); |
| } |
| |
| class InlineTable extends SortedTable { |
| InlineTable(columns) : super(columns); |
| } |
| |
| @CustomTag('code-view') |
| class CodeViewElement extends ObservatoryElement { |
| @observable Code code; |
| ProfileCode get profile => code == null ? null : code.profile; |
| DisassemblyTable disassemblyTable; |
| InlineTable inlineTable; |
| |
| CodeViewElement.created() : super.created() { |
| // Create table models. |
| var columns = [ |
| new SortedTableColumn('Address'), |
| new SortedTableColumn('Inclusive'), |
| new SortedTableColumn('Exclusive'), |
| new SortedTableColumn('Disassembly'), |
| ]; |
| disassemblyTable = new DisassemblyTable(columns); |
| columns = [ |
| new SortedTableColumn('Address'), |
| new SortedTableColumn('Inclusive'), |
| new SortedTableColumn('Exclusive'), |
| new SortedTableColumn('Functions'), |
| ]; |
| inlineTable = new InlineTable(columns); |
| } |
| |
| @override |
| void attached() { |
| super.attached(); |
| } |
| |
| void codeChanged(oldValue) { |
| if (code == null) { |
| return; |
| } |
| code.load().then((Code c) { |
| c.loadScript(); |
| _updateDisassembly(); |
| _updateInline(); |
| }); |
| } |
| |
| Future refresh() { |
| return code.reload(); |
| } |
| |
| Future refreshTicks() { |
| var isolate = code.isolate; |
| return isolate.invokeRpc('_getCpuProfile', { 'tags': 'None' }) |
| .then((ServiceMap response) { |
| var cpuProfile = new CpuProfile(); |
| cpuProfile.load(isolate, response); |
| _updateDisassembly(); |
| _updateInline(); |
| }); |
| } |
| |
| String formattedAddress(CodeInstruction instruction) { |
| if (instruction.address == 0) { |
| return ''; |
| } |
| return '0x${instruction.address.toRadixString(16)}'; |
| } |
| |
| String formattedAddressRange(CodeInlineInterval interval) { |
| String start = interval.start.toRadixString(16); |
| String end = interval.end.toRadixString(16); |
| return '[0x$start, 0x$end)'; |
| } |
| |
| String formattedInclusiveInterval(CodeInlineInterval interval) { |
| if (code == null) { |
| return ''; |
| } |
| if (code.profile == null) { |
| return ''; |
| } |
| var intervalTick = code.profile.intervalTicks[interval.start]; |
| if (intervalTick == null) { |
| return ''; |
| } |
| // Don't show inclusive ticks if they are the same as exclusive ticks. |
| if (intervalTick.inclusiveTicks == intervalTick.exclusiveTicks) { |
| return ''; |
| } |
| var pcent = Utils.formatPercent(intervalTick.inclusiveTicks, |
| code.profile.profile.sampleCount); |
| return '$pcent (${intervalTick.inclusiveTicks})'; |
| } |
| |
| String formattedExclusiveInterval(CodeInlineInterval interval) { |
| if (code == null) { |
| return ''; |
| } |
| if (code.profile == null) { |
| return ''; |
| } |
| var intervalTick = code.profile.intervalTicks[interval.start]; |
| if (intervalTick == null) { |
| return ''; |
| } |
| var pcent = Utils.formatPercent(intervalTick.exclusiveTicks, |
| code.profile.profile.sampleCount); |
| return '$pcent (${intervalTick.exclusiveTicks})'; |
| } |
| |
| |
| String formattedInclusive(CodeInstruction instruction) { |
| if (code == null) { |
| return ''; |
| } |
| if (code.profile == null) { |
| return ''; |
| } |
| var tick = code.profile.addressTicks[instruction.address]; |
| if (tick == null) { |
| return ''; |
| } |
| // Don't show inclusive ticks if they are the same as exclusive ticks. |
| if (tick.inclusiveTicks == tick.exclusiveTicks) { |
| return ''; |
| } |
| var pcent = Utils.formatPercent(tick.inclusiveTicks, |
| code.profile.profile.sampleCount); |
| return '$pcent (${tick.inclusiveTicks})'; |
| } |
| |
| String formattedExclusive(CodeInstruction instruction) { |
| if (code == null) { |
| return ''; |
| } |
| if (code.profile == null) { |
| return ''; |
| } |
| var tick = code.profile.addressTicks[instruction.address]; |
| if (tick == null) { |
| return ''; |
| } |
| var pcent = Utils.formatPercent(tick.exclusiveTicks, |
| code.profile.profile.sampleCount); |
| return '$pcent (${tick.exclusiveTicks})'; |
| } |
| |
| void _updateDiasssemblyTable() { |
| disassemblyTable.clearRows(); |
| if (code == null) { |
| return; |
| } |
| for (CodeInstruction instruction in code.instructions) { |
| var row = [formattedAddress(instruction), |
| formattedInclusive(instruction), |
| formattedExclusive(instruction), |
| instruction.human]; |
| disassemblyTable.addRow(new SortedTableRow(row)); |
| } |
| } |
| |
| void _addDisassemblyDOMRow() { |
| var tableBody = $['disassemblyTableBody']; |
| assert(tableBody != null); |
| var tr = new TableRowElement(); |
| |
| var cell; |
| |
| // Add new space. |
| cell = tr.insertCell(-1); |
| cell.classes.add('monospace'); |
| cell = tr.insertCell(-1); |
| cell.classes.add('monospace'); |
| cell = tr.insertCell(-1); |
| cell.classes.add('monospace'); |
| cell = tr.insertCell(-1); |
| cell.classes.add('monospace'); |
| |
| tableBody.children.add(tr); |
| } |
| |
| void _fillDisassemblyDOMRow(TableRowElement tr, int rowIndex) { |
| final row = disassemblyTable.rows[rowIndex]; |
| final n = row.values.length; |
| for (var i = 0; i < n; i++) { |
| final cell = tr.children[i]; |
| cell.title = row.values[i].toString(); |
| cell.text = row.values[i].toString(); |
| } |
| } |
| |
| void _updateDisassemblyDOMTable() { |
| var tableBody = $['disassemblyTableBody']; |
| assert(tableBody != null); |
| // Resize DOM table. |
| if (tableBody.children.length > disassemblyTable.sortedRows.length) { |
| // Shrink the table. |
| var deadRows = |
| tableBody.children.length - disassemblyTable.sortedRows.length; |
| for (var i = 0; i < deadRows; i++) { |
| tableBody.children.removeLast(); |
| } |
| } else if (tableBody.children.length < disassemblyTable.sortedRows.length) { |
| // Grow table. |
| var newRows = |
| disassemblyTable.sortedRows.length - tableBody.children.length; |
| for (var i = 0; i < newRows; i++) { |
| _addDisassemblyDOMRow(); |
| } |
| } |
| |
| assert(tableBody.children.length == disassemblyTable.sortedRows.length); |
| |
| // Fill table. |
| var i = 0; |
| for (var tr in tableBody.children) { |
| var rowIndex = disassemblyTable.sortedRows[i]; |
| _fillDisassemblyDOMRow(tr, rowIndex); |
| i++; |
| } |
| } |
| |
| void _updateDisassembly() { |
| _updateDiasssemblyTable(); |
| _updateDisassemblyDOMTable(); |
| } |
| |
| void _updateInlineTable() { |
| inlineTable.clearRows(); |
| if (code == null) { |
| return; |
| } |
| for (CodeInlineInterval interval in code.inlineIntervals) { |
| var row = [interval, |
| formattedInclusiveInterval(interval), |
| formattedExclusiveInterval(interval), |
| interval.functions]; |
| inlineTable.addRow(new SortedTableRow(row)); |
| } |
| } |
| |
| void _addInlineDOMRow() { |
| var tableBody = shadowRoot.querySelector('#inlineRangeTableBody'); |
| assert(tableBody != null); |
| var tr = new TableRowElement(); |
| |
| var cell; |
| |
| // Add new space. |
| cell = tr.insertCell(-1); |
| cell.classes.add('monospace'); |
| cell = tr.insertCell(-1); |
| cell.classes.add('monospace'); |
| cell = tr.insertCell(-1); |
| cell.classes.add('monospace'); |
| cell = tr.insertCell(-1); |
| |
| tableBody.children.add(tr); |
| } |
| |
| void _fillInlineDOMRow(TableRowElement tr, int rowIndex) { |
| var row = inlineTable.rows[rowIndex]; |
| var columns = row.values.length; |
| var addressRangeColumn = 0; |
| var functionsColumn = columns - 1; |
| |
| { |
| var addressRangeCell = tr.children[addressRangeColumn]; |
| var interval = row.values[addressRangeColumn]; |
| var addressRangeString = formattedAddressRange(interval); |
| var addressRangeElement = new SpanElement(); |
| addressRangeElement.classes.add('monospace'); |
| addressRangeElement.text = addressRangeString; |
| addressRangeCell.children.clear(); |
| addressRangeCell.children.add(addressRangeElement); |
| } |
| |
| for (var i = addressRangeColumn + 1; i < columns - 1; i++) { |
| var cell = tr.children[i]; |
| cell.title = row.values[i].toString(); |
| cell.text = row.values[i].toString(); |
| } |
| var functions = row.values[functionsColumn]; |
| var functionsCell = tr.children[functionsColumn]; |
| functionsCell.children.clear(); |
| for (var func in functions) { |
| var functionRef = new Element.tag('function-ref'); |
| functionRef.ref = func; |
| functionsCell.children.add(functionRef); |
| var gap = new SpanElement(); |
| gap.style.minWidth = '1em'; |
| gap.text = ' '; |
| functionsCell.children.add(gap); |
| } |
| } |
| |
| void _updateInlineDOMTable() { |
| var tableBody = shadowRoot.querySelector('#inlineRangeTableBody'); |
| // Resize DOM table. |
| if (tableBody.children.length > inlineTable.sortedRows.length) { |
| // Shrink the table. |
| var deadRows = |
| tableBody.children.length - inlineTable.sortedRows.length; |
| for (var i = 0; i < deadRows; i++) { |
| tableBody.children.removeLast(); |
| } |
| } else if (tableBody.children.length < inlineTable.sortedRows.length) { |
| // Grow table. |
| var newRows = inlineTable.sortedRows.length - tableBody.children.length; |
| for (var i = 0; i < newRows; i++) { |
| _addInlineDOMRow(); |
| } |
| } |
| assert(tableBody.children.length == inlineTable.sortedRows.length); |
| // Fill table. |
| for (var i = 0; i < inlineTable.sortedRows.length; i++) { |
| var rowIndex = inlineTable.sortedRows[i]; |
| var tr = tableBody.children[i]; |
| _fillInlineDOMRow(tr, rowIndex); |
| } |
| } |
| |
| void _updateInline() { |
| _updateInlineTable(); |
| _updateInlineDOMTable(); |
| } |
| |
| Element _findJumpTarget(Element target) { |
| var jumpTarget = target.attributes['data-jump-target']; |
| if (jumpTarget == '') { |
| return null; |
| } |
| var address = int.parse(jumpTarget); |
| var node = shadowRoot.querySelector('#addr-$address'); |
| if (node == null) { |
| return null; |
| } |
| return node; |
| } |
| |
| void mouseOver(Event e, var detail, Node target) { |
| var jt = _findJumpTarget(target); |
| if (jt == null) { |
| return; |
| } |
| jt.classes.add('highlight'); |
| } |
| |
| void mouseOut(Event e, var detail, Node target) { |
| var jt = _findJumpTarget(target); |
| if (jt == null) { |
| return; |
| } |
| jt.classes.remove('highlight'); |
| } |
| } |