v8/tools/system-analyzer/view/list-panel.mjs
Camillo Bruni e24deb89fe [tools] Various system-analyzer fixes
- Handle empty script sources
- Fix list-panel groups, order by count

Bug: v8:10644
Change-Id: I03d3915f709d47429040b591c0271e951eca58e9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3289642
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Auto-Submit: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Patrick Thier <pthier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78165}
2021-11-30 15:16:21 +00:00

197 lines
5.6 KiB
JavaScript

// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {App} from '../index.mjs'
import {FocusEvent, ToolTipEvent} from './events.mjs';
import {groupBy, LazyTable} from './helper.mjs';
import {CollapsableElement, DOM} from './helper.mjs';
DOM.defineCustomElement('view/list-panel',
(templateText) =>
class ListPanel extends CollapsableElement {
_selectedLogEntries = [];
_displayedLogEntries = [];
_timeline;
_detailsClickHandler = this._handleDetailsClick.bind(this);
_logEntryClickHandler = this._handleLogEntryClick.bind(this);
_logEntryMouseOverHandler = this._logEntryMouseOverHandler.bind(this);
constructor() {
super(templateText);
this.groupKey.addEventListener('change', e => this.requestUpdate());
this.showAllRadio.onclick = _ => this._showEntries(this._timeline);
this.showTimerangeRadio.onclick = _ =>
this._showEntries(this._timeline.selectionOrSelf);
this.showSelectionRadio.onclick = _ =>
this._showEntries(this._selectedLogEntries);
}
static get observedAttributes() {
return ['title'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name == 'title') {
this.$('#title').innerHTML = newValue;
}
}
set timeline(timeline) {
console.assert(timeline !== undefined, 'timeline undefined!');
this._timeline = timeline;
this.$('.panel').style.display = timeline.isEmpty() ? 'none' : 'inherit';
this._initGroupKeySelect();
}
set selectedLogEntries(entries) {
if (entries === this._timeline) {
this.showAllRadio.click();
} else if (entries === this._timeline.selection) {
this.showTimerangeRadio.click();
} else {
this._selectedLogEntries = entries;
this.showSelectionRadio.click();
}
}
get entryClass() {
return this._timeline.at(0)?.constructor;
}
get groupKey() {
return this.$('#group-key');
}
get table() {
return this.$('#table');
}
get showAllRadio() {
return this.$('#show-all');
}
get showTimerangeRadio() {
return this.$('#show-timerange');
}
get showSelectionRadio() {
return this.$('#show-selection');
}
get _propertyNames() {
return this.entryClass?.propertyNames ?? [];
}
_initGroupKeySelect() {
const select = this.groupKey;
select.options.length = 0;
for (const propertyName of this._propertyNames) {
const option = DOM.element('option');
option.text = propertyName;
select.add(option);
}
}
_showEntries(entries) {
this._displayedLogEntries = entries;
this.requestUpdate();
}
_update() {
if (this._timeline.isEmpty()) return;
DOM.removeAllChildren(this.table);
if (this._displayedLogEntries.length == 0) return;
const propertyName = this.groupKey.selectedOptions[0].text;
const groups =
groupBy(this._displayedLogEntries, each => each[propertyName], true);
this._render(groups, this.table);
}
createSubgroups(group) {
const map = new Map();
const tempGroups = [];
for (let propertyName of this._propertyNames) {
map.set(
propertyName,
groupBy(group.entries, each => each[propertyName], true));
}
return map;
}
_handleLogEntryClick(e) {
const group = e.currentTarget.group;
this.dispatchEvent(new FocusEvent(group.key));
}
_logEntryMouseOverHandler(e) {
const group = e.currentTarget.group;
this.dispatchEvent(new ToolTipEvent(group.key, e.currentTarget));
}
_handleDetailsClick(event) {
event.stopPropagation();
const tr = event.target.parentNode;
const group = tr.group;
// Create subgroup in-place if the don't exist yet.
if (tr.groups === undefined) {
const groups = tr.groups = this.createSubgroups(group);
this.renderDrilldown(groups, tr);
}
const detailsTr = tr.nextSibling;
if (tr.classList.contains('open')) {
tr.classList.remove('open');
detailsTr.style.display = 'none';
} else {
tr.classList.add('open');
detailsTr.style.display = 'table-row';
}
}
renderDrilldown(groups, previousSibling) {
const tr = DOM.tr('entry-details');
tr.style.display = 'none';
// indent by one td.
tr.appendChild(DOM.td());
const td = DOM.td();
td.colSpan = 3;
groups.forEach((group, key) => {
this.renderDrilldownGroup(td, group, key);
});
tr.appendChild(td);
// Append the new TR after previousSibling.
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling);
}
renderDrilldownGroup(td, groups, key) {
const div = DOM.div('drilldown-group-title');
div.textContent = `Grouped by ${key}: ${groups[0]?.parentTotal ?? 0}#`;
td.appendChild(div);
const table = DOM.table();
this._render(groups, table, false)
td.appendChild(table);
}
_render(groups, table) {
let last;
new LazyTable(table, groups, 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.length, 'count'));
const valueTd = tr.appendChild(DOM.td(group.key?.toString(), 'key'));
if (App.isClickable(group.key)) {
tr.onclick = this._logEntryClickHandler;
tr.onmouseover = this._logEntryMouseOverHandler;
valueTd.classList.add('clickable');
}
return tr;
}, 10);
}
});