v8/tools/system-analyzer/ic-panel.mjs
Zeynep Cankara 34b652607a [tools][system-analyzer] Change naming conventions
This CL establishes a naming consistency
across the app by renaming classes.

Class Name Changes:
SelectEvent -> FocusEvent
Entry -> IcLogEvent
V8Map -> MapLogEvent

Bug: v8:10644
Change-Id: Id075d9aa36ac6f03af0224feb0e38985b1445013
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2349300
Commit-Queue: Zeynep Cankara <zcankara@google.com>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69382}
2020-08-13 13:39:44 +00:00

199 lines
5.8 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 { Group } from './ic-model.mjs';
import CustomIcProcessor from "./ic-processor.mjs";
import { FocusEvent, SelectTimeEvent } from './events.mjs';
import { defineCustomElement, V8CustomElement } from './helper.mjs';
defineCustomElement('ic-panel', (templateText) =>
class ICPanel extends V8CustomElement {
//TODO(zcankara) Entries never set
#entries;
#filteredEntries;
constructor() {
super(templateText);
this.groupKey.addEventListener(
'change', e => this.updateTable(e));
this.$('#filterICTimeBtn').addEventListener(
'click', e => this.handleICTimeFilter(e));
}
get entries() {
return this.#entries;
}
get groupKey() {
return this.$('#group-key');
}
get table() {
return this.$('#table');
}
get tableBody() {
return this.$('#table-body');
}
get count() {
return this.$('#count');
}
get spanSelectAll() {
return this.querySelectorAll("span");
}
set filteredEntries(value) {
this.#filteredEntries = value;
this.updateTable();
}
get filteredEntries() {
return this.#filteredEntries;
}
updateTable(event) {
let select = this.groupKey;
let key = select.options[select.selectedIndex].text;
let tableBody = this.tableBody;
this.removeAllChildren(tableBody);
let groups = Group.groupBy(this.filteredEntries, key, true);
this.render(groups, tableBody);
}
escapeHtml(unsafe) {
if (!unsafe) return "";
return unsafe.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
processValue(unsafe) {
if (!unsafe) return "";
if (!unsafe.startsWith("http")) return this.escapeHtml(unsafe);
let a = document.createElement("a");
a.href = unsafe;
a.textContent = unsafe;
return a;
}
td(tr, content, className) {
let node = document.createElement("td");
if (typeof content == "object") {
node.appendChild(content);
} else {
node.innerHTML = content;
}
node.className = className;
tr.appendChild(node);
return node
}
handleMapClick(e) {
this.dispatchEvent(new FocusEvent(e.target.parentNode.entry));
}
handleFilePositionClick(e) {
this.dispatchEvent(new FocusEvent(e.target.parentNode.entry.key));
}
render(entries, parent) {
let fragment = document.createDocumentFragment();
let max = Math.min(1000, entries.length)
for (let i = 0; i < max; i++) {
let entry = entries[i];
let tr = document.createElement("tr");
tr.entry = entry;
//TODO(zcankara) Create one bound method and use it everywhere
if (entry.property === "map") {
tr.addEventListener('click', e => this.handleMapClick(e));
} else if (entry.property == "filePosition") {
tr.addEventListener('click',
e => this.handleFilePositionClick(e));
}
let details = this.td(tr, '<span>&#8505;</a>', 'details');
//TODO(zcankara) don't keep the whole function context alive
details.onclick = _ => this.toggleDetails(details);
this.td(tr, entry.percentage + "%", 'percentage');
this.td(tr, entry.count, 'count');
this.td(tr, this.processValue(entry.key), 'key');
fragment.appendChild(tr);
}
let omitted = entries.length - max;
if (omitted > 0) {
let tr = document.createElement("tr");
let tdNode = this.td(tr, 'Omitted ' + omitted + " entries.");
tdNode.colSpan = 4;
fragment.appendChild(tr);
}
parent.appendChild(fragment);
}
renderDrilldown(entry, previousSibling) {
let tr = document.createElement('tr');
tr.className = "entry-details";
tr.style.display = "none";
// indent by one td.
tr.appendChild(document.createElement("td"));
let td = document.createElement("td");
td.colSpan = 3;
for (let key in entry.groups) {
td.appendChild(this.renderDrilldownGroup(entry, key));
}
tr.appendChild(td);
// Append the new TR after previousSibling.
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling)
}
renderDrilldownGroup(entry, key) {
let max = 20;
let group = entry.groups[key];
let div = document.createElement("div")
div.className = 'drilldown-group-title'
div.textContent = key + ' [top ' + max + ' out of ' + group.length + ']';
let table = document.createElement("table");
this.render(group.slice(0, max), table, false)
div.appendChild(table);
return div;
}
toggleDetails(node) {
let tr = node.parentNode;
let entry = tr.entry;
// Create subgroup in-place if the don't exist yet.
if (entry.groups === undefined) {
entry.createSubGroups();
this.renderDrilldown(entry, tr);
}
let details = tr.nextSibling;
let display = details.style.display;
if (display != "none") {
display = "none";
} else {
display = "table-row"
};
details.style.display = display;
}
initGroupKeySelect() {
let select = this.groupKey;
select.options.length = 0;
for (let i in CustomIcProcessor.kProperties) {
let option = document.createElement("option");
option.text = CustomIcProcessor.kProperties[i];
select.add(option);
}
}
handleICTimeFilter(e) {
this.dispatchEvent(new SelectTimeEvent(
parseInt(this.$('#filter-time-start').value),
parseInt(this.$('#filter-time-end').value)));
}
});