Commit 91ddeb06 authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[tools][system-analyzer] Various improvements

- Show related code object for Maps
- Fix opening transition trees
- Rename *LogEntry.prototype.codeLogEntry to .code
- Show Arrays as dropdowns in tooltips
- Avoid hiding the tooltip when clicking on the tooltip itself
- Show links to code variants (bytecode/baseline/optimized)
- Fix chunk offset calculation
- Fix code for browsers that don't support
  navigator.scheduling.isInputPending

Bug: v8:10644
Change-Id: I858dc410657d26d076214368814a52177b124f4c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2964592
Auto-Submit: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75169}
parent 88f4ca2b
...@@ -57,7 +57,7 @@ export class SourcePosition { ...@@ -57,7 +57,7 @@ export class SourcePosition {
title: this.toString(), title: this.toString(),
__this__: this, __this__: this,
script: this.script, script: this.script,
entries: this.entries.length, entries: this.entries,
} }
} }
} }
...@@ -128,7 +128,7 @@ export class Script { ...@@ -128,7 +128,7 @@ export class Script {
id: this.id, id: this.id,
url: this.url, url: this.url,
source: this.source, source: this.source,
sourcePositions: this.sourcePositions.length sourcePositions: this.sourcePositions
} }
} }
...@@ -203,7 +203,7 @@ export class Profile { ...@@ -203,7 +203,7 @@ export class Profile {
/** /**
* Returns whether a function with the specified name must be skipped. * Returns whether a function with the specified name must be skipped.
* Should be overriden by subclasses. * Should be overridden by subclasses.
* *
* @param {string} name Function name. * @param {string} name Function name.
*/ */
...@@ -840,6 +840,10 @@ class FunctionEntry extends CodeEntry { ...@@ -840,6 +840,10 @@ class FunctionEntry extends CodeEntry {
return this._codeEntries.values().next().value.getSourceCode(); return this._codeEntries.values().next().value.getSourceCode();
} }
get codeEntries() {
return this._codeEntries;
}
/** /**
* Returns node name. * Returns node name.
*/ */
......
...@@ -263,13 +263,16 @@ class App { ...@@ -263,13 +263,16 @@ class App {
this._view.mapTrack.focusedEntry = entry; this._view.mapTrack.focusedEntry = entry;
this._view.mapPanel.map = entry; this._view.mapPanel.map = entry;
this._view.mapPanel.show(); this._view.mapPanel.show();
if (focusSourcePosition) this.focusSourcePosition(entry.sourcePosition); if (focusSourcePosition) {
this.focusCodeLogEntry(entry.code, false);
this.focusSourcePosition(entry.sourcePosition);
}
} }
focusIcLogEntry(entry) { focusIcLogEntry(entry) {
this._state.ic = entry; this._state.ic = entry;
this.focusMapLogEntry(entry.map, false); this.focusMapLogEntry(entry.map, false);
this.focusCodeLogEntry(entry.codeLogEntry, false); this.focusCodeLogEntry(entry.code, false);
this.focusSourcePosition(entry.sourcePosition); this.focusSourcePosition(entry.sourcePosition);
} }
...@@ -282,7 +285,7 @@ class App { ...@@ -282,7 +285,7 @@ class App {
focusDeoptLogEntry(entry) { focusDeoptLogEntry(entry) {
this._state.deoptLogEntry = entry; this._state.deoptLogEntry = entry;
this.focusCodeLogEntry(entry.codeLogEntry, false); this.focusCodeLogEntry(entry.code, false);
this.focusSourcePosition(entry.sourcePosition); this.focusSourcePosition(entry.sourcePosition);
} }
......
...@@ -32,7 +32,7 @@ export class DeoptLogEntry extends LogEntry { ...@@ -32,7 +32,7 @@ export class DeoptLogEntry extends LogEntry {
return this._entry; return this._entry;
} }
get codeLogEntry() { get code() {
return this._entry?.logEntry; return this._entry?.logEntry;
} }
...@@ -43,7 +43,7 @@ export class DeoptLogEntry extends LogEntry { ...@@ -43,7 +43,7 @@ export class DeoptLogEntry extends LogEntry {
static get propertyNames() { static get propertyNames() {
return [ return [
'type', 'reason', 'functionName', 'sourcePosition', 'type', 'reason', 'functionName', 'sourcePosition',
'functionSourcePosition', 'script' 'functionSourcePosition', 'script', 'code'
]; ];
} }
} }
...@@ -85,6 +85,11 @@ export class CodeLogEntry extends LogEntry { ...@@ -85,6 +85,11 @@ export class CodeLogEntry extends LogEntry {
return this._entry?.source?.disassemble; return this._entry?.source?.disassemble;
} }
get variants() {
const entries = Array.from(this.entry?.func?.codeEntries ?? []);
return entries.map(each => each.logEntry);
}
toString() { toString() {
return `Code(${this.type})`; return `Code(${this.type})`;
} }
...@@ -98,7 +103,7 @@ export class CodeLogEntry extends LogEntry { ...@@ -98,7 +103,7 @@ export class CodeLogEntry extends LogEntry {
static get propertyNames() { static get propertyNames() {
return [ return [
'functionName', 'sourcePosition', 'kindName', 'size', 'type', 'kind', 'functionName', 'sourcePosition', 'kindName', 'size', 'type', 'kind',
'script', 'source', 'code' 'script', 'source', 'code', 'variants'
]; ];
} }
} }
......
...@@ -30,7 +30,7 @@ export class IcLogEntry extends LogEntry { ...@@ -30,7 +30,7 @@ export class IcLogEntry extends LogEntry {
return this.oldState + ' → ' + this.newState; return this.oldState + ' → ' + this.newState;
} }
get codeLogEntry() { get code() {
return this.codeEntry?.logEntry; return this.codeEntry?.logEntry;
} }
...@@ -62,8 +62,8 @@ export class IcLogEntry extends LogEntry { ...@@ -62,8 +62,8 @@ export class IcLogEntry extends LogEntry {
static get propertyNames() { static get propertyNames() {
return [ return [
'type', 'category', 'functionName', 'script', 'sourcePosition', 'state', 'type', 'category', 'functionName', 'script', 'sourcePosition', 'code',
'key', 'map', 'reason' 'state', 'key', 'map', 'reason'
]; ];
} }
} }
...@@ -7,8 +7,9 @@ import {LogEntry} from './log.mjs'; ...@@ -7,8 +7,9 @@ import {LogEntry} from './log.mjs';
// =========================================================================== // ===========================================================================
// Map Log Events // Map Log Events
const kChunkHeight = 200; export const kChunkHeight = 200;
const kChunkWidth = 10; export const kChunkWidth = 10;
export const kChunkVisualWidth = 6;
function define(prototype, name, fn) { function define(prototype, name, fn) {
Object.defineProperty(prototype, name, {value: fn, enumerable: false}); Object.defineProperty(prototype, name, {value: fn, enumerable: false});
...@@ -33,7 +34,7 @@ define(Array.prototype, 'last', function() { ...@@ -33,7 +34,7 @@ define(Array.prototype, 'last', function() {
// =========================================================================== // ===========================================================================
// Map Log Events // Map Log Events
class MapLogEntry extends LogEntry { export class MapLogEntry extends LogEntry {
constructor(id, time) { constructor(id, time) {
if (!time) throw new Error('Invalid time'); if (!time) throw new Error('Invalid time');
// Use MapLogEntry.type getter instead of property, since we only know the // Use MapLogEntry.type getter instead of property, since we only know the
...@@ -56,6 +57,10 @@ class MapLogEntry extends LogEntry { ...@@ -56,6 +57,10 @@ class MapLogEntry extends LogEntry {
return this.entry?.functionName; return this.entry?.functionName;
} }
get code() {
return this.entry?.logEntry;
}
toString() { toString() {
return `Map(${this.id})`; return `Map(${this.id})`;
} }
...@@ -121,7 +126,7 @@ class MapLogEntry extends LogEntry { ...@@ -121,7 +126,7 @@ class MapLogEntry extends LogEntry {
position(chunks) { position(chunks) {
const index = this.chunkIndex(chunks); const index = this.chunkIndex(chunks);
if (index === -1) return [0, 0]; if (index === -1) return [0, 0];
const xFrom = (index + 0.5) * kChunkWidth | 0; const xFrom = (index * kChunkWidth + kChunkVisualWidth / 2) | 0;
const yFrom = kChunkHeight - chunks[index].yOffset(this) | 0; const yFrom = kChunkHeight - chunks[index].yOffset(this) | 0;
return [xFrom, yFrom]; return [xFrom, yFrom];
} }
...@@ -189,8 +194,8 @@ class MapLogEntry extends LogEntry { ...@@ -189,8 +194,8 @@ class MapLogEntry extends LogEntry {
static get propertyNames() { static get propertyNames() {
return [ return [
'type', 'reason', 'property', 'functionName', 'sourcePosition', 'script', 'type', 'reason', 'property', 'parent', 'functionName', 'sourcePosition',
'id', 'parent', 'description' 'script', 'code', 'id', 'description'
]; ];
} }
} }
...@@ -198,7 +203,7 @@ class MapLogEntry extends LogEntry { ...@@ -198,7 +203,7 @@ class MapLogEntry extends LogEntry {
MapLogEntry.cache = new Map(); MapLogEntry.cache = new Map();
// =========================================================================== // ===========================================================================
class Edge { export class Edge {
constructor(type, name, reason, time, from, to) { constructor(type, name, reason, time, from, to) {
this.type = type; this.type = type;
this.name = name; this.name = name;
...@@ -321,6 +326,4 @@ class Edge { ...@@ -321,6 +326,4 @@ class Edge {
return this.type + ' ' + (this.reason ? this.reason : '') + ' ' + return this.type + ' ' + (this.reason ? this.reason : '') + ' ' +
(this.name ? this.name : '') (this.name ? this.name : '')
} }
} }
\ No newline at end of file
export {MapLogEntry, Edge, kChunkWidth, kChunkHeight};
...@@ -402,9 +402,9 @@ export class Processor extends LogReader { ...@@ -402,9 +402,9 @@ export class Processor extends LogReader {
} }
// TODO: use SourcePosition directly. // TODO: use SourcePosition directly.
let edge = new Edge(type, name, reason, time, from_, to_); let edge = new Edge(type, name, reason, time, from_, to_);
const profileEntry = this._profile.findEntry(pc) const codeEntry = this._profile.findEntry(pc)
to_.entry = profileEntry; to_.entry = codeEntry;
let script = this.getProfileEntryScript(profileEntry); let script = this.getProfileEntryScript(codeEntry);
if (script) { if (script) {
to_.sourcePosition = script.addSourcePosition(line, column, to_) to_.sourcePosition = script.addSourcePosition(line, column, to_)
} }
......
...@@ -36,10 +36,12 @@ DOM.defineCustomElement('view/code-panel', ...@@ -36,10 +36,12 @@ DOM.defineCustomElement('view/code-panel',
'__this__': entry, '__this__': entry,
functionName: entry.functionName, functionName: entry.functionName,
size: formatBytes(entry.size), size: formatBytes(entry.size),
creationTime: formatMicroSeconds(entry.time / 1000),
sourcePosition: entry.sourcePosition, sourcePosition: entry.sourcePosition,
script: entry.script, script: entry.script,
type: entry.type, type: entry.type,
kind: entry.kindName, kind: entry.kindName,
variants: entry.variants,
}; };
} else { } else {
this.$('#properties').propertyDict = {}; this.$('#properties').propertyDict = {};
......
...@@ -186,7 +186,7 @@ DOM.defineCustomElement('view/list-panel', ...@@ -186,7 +186,7 @@ DOM.defineCustomElement('view/list-panel',
details.onclick = this._detailsClickHandler; details.onclick = this._detailsClickHandler;
tr.appendChild(DOM.td(`${group.percent.toFixed(2)}%`, 'percentage')); tr.appendChild(DOM.td(`${group.percent.toFixed(2)}%`, 'percentage'));
tr.appendChild(DOM.td(group.count, 'count')); tr.appendChild(DOM.td(group.count, 'count'));
const valueTd = tr.appendChild(DOM.td(group.key.toString(), 'key')); const valueTd = tr.appendChild(DOM.td(group.key?.toString(), 'key'));
if (App.isClickable(group.key)) { if (App.isClickable(group.key)) {
tr.onclick = this._logEntryClickHandler; tr.onclick = this._logEntryClickHandler;
tr.onmouseover = this._logEntryMouseOverHandler; tr.onmouseover = this._logEntryMouseOverHandler;
......
...@@ -11,7 +11,7 @@ DOM.defineCustomElement( ...@@ -11,7 +11,7 @@ DOM.defineCustomElement(
template => class PropertyLinkTable extends V8CustomElement { template => class PropertyLinkTable extends V8CustomElement {
_instance; _instance;
_propertyDict; _propertyDict;
_instanceLinkButtons = true; _instanceLinkButtons = false;
_logEntryClickHandler = this._handleLogEntryClick.bind(this); _logEntryClickHandler = this._handleLogEntryClick.bind(this);
_logEntryRelatedHandler = this._handleLogEntryRelated.bind(this); _logEntryRelatedHandler = this._handleLogEntryRelated.bind(this);
...@@ -19,12 +19,8 @@ DOM.defineCustomElement( ...@@ -19,12 +19,8 @@ DOM.defineCustomElement(
super(template); super(template);
} }
static get observedAttributes() { set instanceLinkButtons(newValue) {
return ['noButtons']; this._instanceLinkButtons = newValue;
}
attributeChangedCallback(name, oldValue, newValue) {
if (name == 'instanceLinkButtons') this._instanceLinkButtons = true;
} }
set propertyDict(propertyDict) { set propertyDict(propertyDict) {
...@@ -63,6 +59,10 @@ DOM.defineCustomElement( ...@@ -63,6 +59,10 @@ DOM.defineCustomElement(
row.insertCell().innerText = key; row.insertCell().innerText = key;
const cell = row.insertCell(); const cell = row.insertCell();
if (value == undefined) return; if (value == undefined) return;
if (Array.isArray(value)) {
cell.appendChild(this._addArrayValue(value));
return;
}
if (App.isClickable(value)) { if (App.isClickable(value)) {
cell.className = 'clickable'; cell.className = 'clickable';
cell.onclick = this._logEntryClickHandler; cell.onclick = this._logEntryClickHandler;
...@@ -71,6 +71,22 @@ DOM.defineCustomElement( ...@@ -71,6 +71,22 @@ DOM.defineCustomElement(
new ExpandableText(cell, value.toString()); new ExpandableText(cell, value.toString());
} }
_addArrayValue(array) {
if (array.length == 0) {
return DOM.text('empty');
} else if (array.length > 200) {
return DOM.text(`${array.length} items`);
}
const select = DOM.element('select');
for (let value of array) {
const option = DOM.element('option');
option.innerText = value.toString();
option.data = value;
select.add(option);
}
return select;
}
_addTitle(value) { _addTitle(value) {
const title = DOM.element('h3'); const title = DOM.element('h3');
title.innerText = value; title.innerText = value;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import {delay} from '../../helper.mjs'; import {delay} from '../../helper.mjs';
import {kChunkHeight, kChunkWidth} from '../../log/map.mjs'; import {kChunkHeight, kChunkVisualWidth, kChunkWidth} from '../../log/map.mjs';
import {SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent, ToolTipEvent,} from '../events.mjs'; import {SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent, ToolTipEvent,} from '../events.mjs';
import {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs'; import {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs';
...@@ -12,7 +12,7 @@ export const kTimelineHeight = 200; ...@@ -12,7 +12,7 @@ export const kTimelineHeight = 200;
export class TimelineTrackBase extends V8CustomElement { export class TimelineTrackBase extends V8CustomElement {
_timeline; _timeline;
_nofChunks = 500; _nofChunks = 500;
_chunks; _chunks = [];
_selectedEntry; _selectedEntry;
_timeToPixel; _timeToPixel;
_timeStartPixelOffset; _timeStartPixelOffset;
...@@ -275,8 +275,7 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -275,8 +275,7 @@ export class TimelineTrackBase extends V8CustomElement {
lastHeight -= height; lastHeight -= height;
const color = this._legend.colorForType(group.key); const color = this._legend.colorForType(group.key);
buffer += `<rect x=${chunkIndex * kChunkWidth} y=${lastHeight} height=${ buffer += `<rect x=${chunkIndex * kChunkWidth} y=${lastHeight} height=${
height} ` height} width=${kChunkVisualWidth} fill=${color} />`
buffer += `width=6 fill=${color} />`
} }
return buffer; return buffer;
} }
...@@ -365,6 +364,7 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -365,6 +364,7 @@ export class TimelineTrackBase extends V8CustomElement {
style.left = `${chunk.index * kChunkWidth}px`; style.left = `${chunk.index * kChunkWidth}px`;
style.top = `${kTimelineHeight - chunk.height}px`; style.top = `${kTimelineHeight - chunk.height}px`;
style.height = `${chunk.height}px`; style.height = `${chunk.height}px`;
style.width = `${kChunkVisualWidth}px`;
return logEntry; return logEntry;
} }
}; };
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import {MapLogEntry} from '../../log/map.mjs'; import {kChunkVisualWidth, MapLogEntry} from '../../log/map.mjs';
import {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs'; import {CSSColor, DOM} from '../helper.mjs';
import {TimelineTrackBase} from './timeline-track-base.mjs' import {TimelineTrackBase} from './timeline-track-base.mjs'
......
...@@ -267,7 +267,8 @@ class Annotations { ...@@ -267,7 +267,8 @@ class Annotations {
this._markFlames(start - range, start - offset); this._markFlames(start - range, start - offset);
this._markFlames(start + offset, start + range); this._markFlames(start + offset, start + range);
offset = range; offset = range;
if (navigator.scheduling.isInputPending({includeContinuous: true}) || if ((navigator?.scheduling?.isInputPending({includeContinuous: true}) ??
false) ||
performance.now() >= deadline) { performance.now() >= deadline) {
// Yield if we have to handle an input event, or we're out of time. // Yield if we have to handle an input event, or we're out of time.
await delay(50); await delay(50);
......
...@@ -105,7 +105,7 @@ found in the LICENSE file. --> ...@@ -105,7 +105,7 @@ found in the LICENSE file. -->
<div id="body"> <div id="body">
<div id="content"> <div id="content">
<property-link-table id="properties" showButtons></property-link-table> <property-link-table id="properties"></property-link-table>
</div> </div>
<div class="tip"> <div class="tip">
<div class="tipThin"></div> <div class="tipThin"></div>
......
...@@ -20,7 +20,15 @@ DOM.defineCustomElement( ...@@ -20,7 +20,15 @@ DOM.defineCustomElement(
this.requestUpdate(true); this.requestUpdate(true);
} }
}); });
document.addEventListener('click', (e) => this.hide()); document.addEventListener('click', (event) => {
// Only hide the tooltip if we click anywhere outside of it.
let target = event.target;
while (target) {
if (target == this) return;
target = target.parentNode;
}
this.hide()
});
} }
_update() { _update() {
...@@ -89,6 +97,7 @@ DOM.defineCustomElement( ...@@ -89,6 +97,7 @@ DOM.defineCustomElement(
this.contentNode.firstChild.propertyDict = content; this.contentNode.firstChild.propertyDict = content;
} else { } else {
const node = DOM.element('property-link-table'); const node = DOM.element('property-link-table');
node.instanceLinkButtons = true;
node.propertyDict = content; node.propertyDict = content;
this._setContentNode(node); this._setContentNode(node);
} }
......
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