[tools] System-analyzer improvements
- Display the source code in the code-panel - Add selection dropdown to code-panel - Add more filter propertyNames to CodeLogEntry - Rename list panel titles to "XXX List" - Add +10, +100 buttons for LazyTables - Add Color.darken Change-Id: Ia41c41c1d6cc949dfe766397ba6b72edc29797aa Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2578945 Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Auto-Submit: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#71674}
This commit is contained in:
parent
888a80c2aa
commit
0f9bf544da
@ -285,6 +285,10 @@ export class CodeEntry {
|
||||
toString() {
|
||||
return this.name + ': ' + this.size.toString(16);
|
||||
}
|
||||
|
||||
getSourceCode() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
class NameGenerator {
|
||||
|
@ -100,7 +100,7 @@ export class Script {
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.name;
|
||||
return `Script(${this.id}): ${this.name}`;
|
||||
}
|
||||
|
||||
toStringLong() {
|
||||
@ -685,6 +685,10 @@ class DynamicFuncCodeEntry extends CodeEntry {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
get functionName() {
|
||||
return this.func.functionName;
|
||||
}
|
||||
|
||||
getSourceCode() {
|
||||
return this.source?.getSourceCode();
|
||||
}
|
||||
@ -728,6 +732,8 @@ class FunctionEntry extends CodeEntry {
|
||||
|
||||
constructor(name) {
|
||||
super(0, name);
|
||||
const index = name.lastIndexOf(' ');
|
||||
this.functionName = 1 <= index ? name.substring(0, index) : '<anonymous>';
|
||||
}
|
||||
|
||||
addDynamicCode(code) {
|
||||
@ -748,10 +754,10 @@ class FunctionEntry extends CodeEntry {
|
||||
getName() {
|
||||
let name = this.name;
|
||||
if (name.length == 0) {
|
||||
name = '<anonymous>';
|
||||
return '<anonymous>';
|
||||
} else if (name.charAt(0) == ' ') {
|
||||
// An anonymous function with location: " aaa.js:10".
|
||||
name = `<anonymous>${name}`;
|
||||
return `<anonymous>${name}`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
export const KB = 1024;
|
||||
export const MB = KB * KB;
|
||||
export const GB = MB * KB;
|
||||
export const kMillis2Seconds = 1 / 1000;
|
||||
export const kMicro2Milli = 1 / 1000;
|
||||
|
||||
export function formatBytes(bytes) {
|
||||
const units = ['B', 'KiB', 'MiB', 'GiB'];
|
||||
@ -18,8 +18,8 @@ export function formatBytes(bytes) {
|
||||
return bytes.toFixed(2) + units[index];
|
||||
}
|
||||
|
||||
export function formatSeconds(millis) {
|
||||
return (millis * kMillis2Seconds).toFixed(2) + 's';
|
||||
export function formatMicroSeconds(millis) {
|
||||
return (millis * kMicro2Milli).toFixed(1) + 'ms';
|
||||
}
|
||||
|
||||
export function delay(time) {
|
||||
|
@ -107,7 +107,7 @@ found in the LICENSE file. -->
|
||||
|
||||
<div class="panels">
|
||||
<map-panel id="map-panel"></map-panel>
|
||||
<list-panel id="ic-list" title="ICs">
|
||||
<list-panel id="ic-list" title="IC List">
|
||||
<div id="legend">
|
||||
<h3>Legend</h3>
|
||||
<dl>
|
||||
@ -128,10 +128,10 @@ found in the LICENSE file. -->
|
||||
</dl>
|
||||
</div>
|
||||
</list-panel>
|
||||
<list-panel id="map-list" title="Maps"></list-panel>
|
||||
<list-panel id="deopt-list" title="Deopts"></list-panel>
|
||||
<list-panel id="code-list" title="Code"></list-panel>
|
||||
<list-panel id="api-list" title="API"></list-panel>
|
||||
<list-panel id="map-list" title="Map Events"></list-panel>
|
||||
<list-panel id="deopt-list" title="Deopt Events"></list-panel>
|
||||
<list-panel id="code-list" title="Code Events"></list-panel>
|
||||
<list-panel id="api-list" title="API Events"></list-panel>
|
||||
<source-panel id="source-panel"></source-panel>
|
||||
<code-panel id="code-panel"></code-panel>
|
||||
</div>
|
||||
|
@ -4,9 +4,18 @@
|
||||
import {LogEntry} from './log.mjs';
|
||||
|
||||
export class ApiLogEntry extends LogEntry {
|
||||
constructor(type, time, name) {
|
||||
constructor(type, time, name, argument) {
|
||||
super(type, time);
|
||||
this._name = name;
|
||||
this._argument = argument;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
get argument() {
|
||||
return this._argument;
|
||||
}
|
||||
|
||||
toString() {
|
||||
@ -18,6 +27,6 @@ export class ApiLogEntry extends LogEntry {
|
||||
}
|
||||
|
||||
static get propertyNames() {
|
||||
return ['type', 'name'];
|
||||
return ['type', 'name', 'argument'];
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export class DeoptLogEntry extends LogEntry {
|
||||
}
|
||||
|
||||
static get propertyNames() {
|
||||
return ['type', 'reason', 'location', 'script', 'sourcePosition'];
|
||||
return ['type', 'reason', 'sourcePosition', 'script'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,6 +48,30 @@ export class CodeLogEntry extends LogEntry {
|
||||
return this._kind;
|
||||
}
|
||||
|
||||
get entry() {
|
||||
return this._entry;
|
||||
}
|
||||
|
||||
get functionName() {
|
||||
return this._entry.functionName;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._entry.size;
|
||||
}
|
||||
|
||||
get script() {
|
||||
return this.sourcePosition?.script;
|
||||
}
|
||||
|
||||
get source() {
|
||||
return this._entry?.getSourceCode() ?? '';
|
||||
}
|
||||
|
||||
get disassemble() {
|
||||
return this._entry?.source?.disassemble;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Code(${this.type})`;
|
||||
}
|
||||
@ -56,11 +80,7 @@ export class CodeLogEntry extends LogEntry {
|
||||
return `Code(${this.type}): ${this._entry.toString()}`;
|
||||
}
|
||||
|
||||
get disassemble() {
|
||||
return this._entry?.source?.disassemble;
|
||||
}
|
||||
|
||||
static get propertyNames() {
|
||||
return ['type', 'kind', 'script', 'sourcePosition'];
|
||||
return ['type', 'kind', 'functionName', 'sourcePosition', 'script'];
|
||||
}
|
||||
}
|
||||
|
@ -48,10 +48,14 @@ class MapLogEntry extends LogEntry {
|
||||
this.deprecatedTargets = null;
|
||||
this.leftId = 0;
|
||||
this.rightId = 0;
|
||||
this.filePosition = '';
|
||||
this.entry = undefined;
|
||||
this.description = '';
|
||||
}
|
||||
|
||||
get functionName() {
|
||||
return this.entry?.functionName;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Map(${this.id})`;
|
||||
}
|
||||
@ -188,7 +192,10 @@ class MapLogEntry extends LogEntry {
|
||||
}
|
||||
|
||||
static get propertyNames() {
|
||||
return ['id', 'type', 'reason', 'property', 'script', 'sourcePosition'];
|
||||
return [
|
||||
'type', 'reason', 'property', 'functionName', 'sourcePosition', 'script',
|
||||
'id'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,7 +334,7 @@ export class Processor extends LogReader {
|
||||
// TODO: use SourcePosition directly.
|
||||
let edge = new Edge(type, name, reason, time, from_, to_);
|
||||
const profileEntry = this._profile.findEntry(pc)
|
||||
to_.filePosition = this.formatProfileEntry(profileEntry, line, column);
|
||||
to_.entry = profileEntry;
|
||||
let script = this.getProfileEntryScript(profileEntry);
|
||||
if (script) {
|
||||
to_.sourcePosition = script.addSourcePosition(line, column, to_)
|
||||
@ -391,14 +391,20 @@ export class Processor extends LogReader {
|
||||
return script;
|
||||
}
|
||||
|
||||
processApiEvent(name, varArgs) {
|
||||
processApiEvent(type, varArgs) {
|
||||
let name, arg1;
|
||||
if (varArgs.length == 0) {
|
||||
varArgs = [name];
|
||||
const index = name.indexOf(':');
|
||||
if (index > 0) name = name.substr(0, index);
|
||||
const index = type.indexOf(':');
|
||||
if (index > 0) {
|
||||
name = type;
|
||||
type = type.substr(0, index);
|
||||
}
|
||||
} else {
|
||||
name = varArgs[0];
|
||||
arg1 = varArgs[1];
|
||||
}
|
||||
this._apiTimeline.push(
|
||||
new ApiLogEntry(name, this._lastTimestamp, varArgs[0]));
|
||||
new ApiLogEntry(type, this._lastTimestamp, name, arg1));
|
||||
}
|
||||
|
||||
get icTimeline() {
|
||||
|
@ -9,7 +9,11 @@ found in the LICENSE file. -->
|
||||
</style>
|
||||
<div class="panel">
|
||||
<h2>Code Panel</h2>
|
||||
<select id="codeSelect"></select>
|
||||
<div class="panelBody">
|
||||
<pre id="code"></pre>
|
||||
<h3>Disassembly</h3>
|
||||
<pre id="disassembly"></pre>
|
||||
<h3>Source Code</h3>
|
||||
<pre id="sourceCode"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@ import {IcLogEntry} from '../log/ic.mjs';
|
||||
import {MapLogEntry} from '../log/map.mjs';
|
||||
|
||||
import {FocusEvent, SelectionEvent, ToolTipEvent} from './events.mjs';
|
||||
import {delay, DOM, formatBytes, V8CustomElement} from './helper.mjs';
|
||||
import {delay, DOM, formatBytes, formatMicroSeconds, V8CustomElement} from './helper.mjs';
|
||||
|
||||
DOM.defineCustomElement(
|
||||
'view/code-panel',
|
||||
@ -16,6 +16,7 @@ DOM.defineCustomElement(
|
||||
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this._codeSelectNode.onchange = this._handleSelectCode.bind(this);
|
||||
}
|
||||
|
||||
set timeline(timeline) {
|
||||
@ -26,8 +27,8 @@ DOM.defineCustomElement(
|
||||
set selectedEntries(entries) {
|
||||
this._selectedEntries = entries;
|
||||
// TODO: add code selection dropdown
|
||||
this._entry = entries.first();
|
||||
this.update();
|
||||
this._updateSelect();
|
||||
this.entry = entries.first();
|
||||
}
|
||||
|
||||
set entry(entry) {
|
||||
@ -35,11 +36,38 @@ DOM.defineCustomElement(
|
||||
this.update();
|
||||
}
|
||||
|
||||
get codeNode() {
|
||||
return this.$('#code');
|
||||
get _disassemblyNode() {
|
||||
return this.$('#disassembly');
|
||||
}
|
||||
|
||||
get _sourceNode() {
|
||||
return this.$('#sourceCode');
|
||||
}
|
||||
|
||||
get _codeSelectNode() {
|
||||
return this.$('#codeSelect');
|
||||
}
|
||||
|
||||
_update() {
|
||||
this.codeNode.innerText = this._entry?.disassemble ?? '';
|
||||
this._disassemblyNode.innerText = this._entry?.disassemble ?? '';
|
||||
this._sourceNode.innerText = this._entry?.source ?? '';
|
||||
}
|
||||
|
||||
_updateSelect() {
|
||||
const select = this._codeSelectNode;
|
||||
select.options.length = 0;
|
||||
const sorted =
|
||||
this._selectedEntries.slice().sort((a, b) => a.time - b.time);
|
||||
for (const code of this._selectedEntries) {
|
||||
const option = DOM.element('option');
|
||||
option.text =
|
||||
`${code.name}(...) t=${formatMicroSeconds(code.time)} size=${
|
||||
formatBytes(code.size)} script=${code.script?.toString()}`;
|
||||
option.data = code;
|
||||
select.add(option);
|
||||
}
|
||||
}
|
||||
_handleSelectCode() {
|
||||
this.entry = this._codeSelectNode.selectedOptions[0].data;
|
||||
}
|
||||
});
|
@ -13,7 +13,8 @@ class CSSColor {
|
||||
if (color === undefined) {
|
||||
throw new Error(`CSS color does not exist: ${name}`);
|
||||
}
|
||||
this._cache.set(name, color.trim());
|
||||
color = color.trim();
|
||||
this._cache.set(name, color);
|
||||
return color;
|
||||
}
|
||||
static reset() {
|
||||
@ -68,15 +69,31 @@ class CSSColor {
|
||||
static get blue() {
|
||||
return this.get('blue');
|
||||
}
|
||||
|
||||
static get orange() {
|
||||
return this.get('orange');
|
||||
}
|
||||
|
||||
static get violet() {
|
||||
return this.get('violet');
|
||||
}
|
||||
|
||||
static at(index) {
|
||||
return this.list[index % this.list.length];
|
||||
}
|
||||
|
||||
static darken(hexColorString, amount = -40) {
|
||||
if (hexColorString[0] !== '#') {
|
||||
throw new Error(`Unsupported color: ${hexColorString}`);
|
||||
}
|
||||
let color = parseInt(hexColorString.substring(1), 16);
|
||||
let b = Math.min(Math.max((color & 0xFF) + amount, 0), 0xFF);
|
||||
let g = Math.min(Math.max(((color >> 8) & 0xFF) + amount, 0), 0xFF);
|
||||
let r = Math.min(Math.max(((color >> 16) & 0xFF) + amount, 0), 0xFF);
|
||||
color = (r << 16) + (g << 8) + b;
|
||||
return `#${color.toString(16).padStart(6, '0')}`;
|
||||
}
|
||||
|
||||
static get list() {
|
||||
if (!this._colors) {
|
||||
this._colors = [
|
||||
@ -89,6 +106,15 @@ class CSSColor {
|
||||
this.blue,
|
||||
this.yellow,
|
||||
this.secondaryColor,
|
||||
this.darken(this.green),
|
||||
this.darken(this.violet),
|
||||
this.darken(this.orange),
|
||||
this.darken(this.yellow),
|
||||
this.darken(this.primaryColor),
|
||||
this.darken(this.red),
|
||||
this.darken(this.blue),
|
||||
this.darken(this.yellow),
|
||||
this.darken(this.secondaryColor),
|
||||
];
|
||||
}
|
||||
return this._colors;
|
||||
@ -206,8 +232,8 @@ class Chunked {
|
||||
this._limit = limit;
|
||||
}
|
||||
|
||||
* next() {
|
||||
for (let i = 0; i < this._limit; i++) {
|
||||
* next(limit = undefined) {
|
||||
for (let i = 0; i < (limit ?? this._limit); i++) {
|
||||
const {value, done} = this._iterator.next();
|
||||
if (done) {
|
||||
this._iterator = undefined;
|
||||
@ -234,25 +260,29 @@ class LazyTable {
|
||||
}
|
||||
if (!table.tFoot) {
|
||||
const td = table.appendChild(DOM.element('tfoot'))
|
||||
.appendChild(DOM.tr('clickable'))
|
||||
.appendChild(DOM.td(`Show more...`));
|
||||
.appendChild(DOM.tr())
|
||||
.appendChild(DOM.td());
|
||||
for (let count of [10, 100]) {
|
||||
const button = DOM.element('button');
|
||||
button.innerText = `+${count}`;
|
||||
button.onclick = (e) => this._addMoreRows(count);
|
||||
td.appendChild(button);
|
||||
}
|
||||
td.setAttribute('colspan', 100);
|
||||
}
|
||||
this._clickHandler = this._addMoreRows.bind(this);
|
||||
table.tFoot.addEventListener('click', this._clickHandler);
|
||||
this._addMoreRows();
|
||||
}
|
||||
|
||||
_addMoreRows() {
|
||||
_addMoreRows(count = undefined) {
|
||||
const fragment = new DocumentFragment();
|
||||
for (let row of this._chunkedRowData.next()) {
|
||||
for (let row of this._chunkedRowData.next(count)) {
|
||||
const tr = this._rowElementCreator(row);
|
||||
fragment.appendChild(tr);
|
||||
}
|
||||
this._table.tBodies[0].appendChild(fragment);
|
||||
if (!this._chunkedRowData.hasMore) {
|
||||
this._table.tFoot.removeEventListener('click', this._clickHandler);
|
||||
this._table.tFoot.style.display = 'none';
|
||||
DOM.removeAllChildren(this._table.tFoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,31 +101,6 @@ DOM.defineCustomElement('view/list-panel',
|
||||
this.dispatchEvent(new FocusEvent(sourcePosition));
|
||||
}
|
||||
|
||||
_render(groups, table) {
|
||||
let last;
|
||||
new LazyTable(table, groups, group => {
|
||||
if (last && last.count < group.count) {
|
||||
console.log(last, group);
|
||||
}
|
||||
last = group;
|
||||
const tr = DOM.tr();
|
||||
tr.group = group;
|
||||
const details = tr.appendChild(DOM.td('', 'toggle'));
|
||||
details.onclick = this._detailsClickHandler;
|
||||
tr.appendChild(DOM.td(`${group.percent.toFixed(2)}%`, 'percentage'));
|
||||
tr.appendChild(DOM.td(group.count, 'count'));
|
||||
const valueTd = tr.appendChild(DOM.td(`${group.key}`, 'key'));
|
||||
if (group.key instanceof MapLogEntry) {
|
||||
tr.onclick = this._mapClickHandler;
|
||||
valueTd.classList.add('clickable');
|
||||
} else if (group.key instanceof SourcePosition) {
|
||||
valueTd.classList.add('clickable');
|
||||
tr.onclick = this._fileClickHandler;
|
||||
}
|
||||
return tr;
|
||||
}, 10);
|
||||
}
|
||||
|
||||
_handleDetailsClick(event) {
|
||||
event.stopPropagation();
|
||||
const tr = event.target.parentNode;
|
||||
@ -160,15 +135,40 @@ DOM.defineCustomElement('view/list-panel',
|
||||
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling);
|
||||
}
|
||||
|
||||
renderDrilldownGroup(td, group, key) {
|
||||
renderDrilldownGroup(td, groups, key) {
|
||||
const div = DOM.div('drilldown-group-title');
|
||||
div.textContent = `Grouped by ${key}`;
|
||||
div.textContent = `Grouped by ${key}: ${groups[0]?.parentTotal ?? 0}#`;
|
||||
td.appendChild(div);
|
||||
const table = DOM.table();
|
||||
this._render(group, table, false)
|
||||
this._render(groups, table, false)
|
||||
td.appendChild(table);
|
||||
}
|
||||
|
||||
_render(groups, table) {
|
||||
let last;
|
||||
new LazyTable(table, groups, group => {
|
||||
if (last && last.count < group.count) {
|
||||
console.log(last, group);
|
||||
}
|
||||
last = group;
|
||||
const tr = DOM.tr();
|
||||
tr.group = group;
|
||||
const details = tr.appendChild(DOM.td('', 'toggle'));
|
||||
details.onclick = this._detailsClickHandler;
|
||||
tr.appendChild(DOM.td(`${group.percent.toFixed(2)}%`, 'percentage'));
|
||||
tr.appendChild(DOM.td(group.count, 'count'));
|
||||
const valueTd = tr.appendChild(DOM.td(`${group.key}`, 'key'));
|
||||
if (group.key instanceof MapLogEntry) {
|
||||
tr.onclick = this._mapClickHandler;
|
||||
valueTd.classList.add('clickable');
|
||||
} else if (group.key instanceof SourcePosition) {
|
||||
valueTd.classList.add('clickable');
|
||||
tr.onclick = this._fileClickHandler;
|
||||
}
|
||||
return tr;
|
||||
}, 10);
|
||||
}
|
||||
|
||||
initGroupKeySelect() {
|
||||
const select = this.groupKey;
|
||||
select.options.length = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user