Commit 0f9bf544 authored by Camillo Bruni's avatar Camillo Bruni Committed by Commit Bot

[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/+/2578945Reviewed-by: 's avatarSathya 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}
parent 888a80c2
......@@ -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,19 +48,39 @@ export class CodeLogEntry extends LogEntry {
return this._kind;
}
toString() {
return `Code(${this.type})`;
get entry() {
return this._entry;
}
toStringLong() {
return `Code(${this.type}): ${this._entry.toString()}`;
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})`;
}
toStringLong() {
return `Code(${this.type}): ${this._entry.toString()}`;
}
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;
}
});
\ No newline at end of file
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment