Reland "[presubmit] Add JS formatting for tools/system-analyzer"
This is a reland of 1ec8f1da4f
Original change's description:
> [presubmit] Add JS formatting for tools/system-analyzer
>
> Bug: v8:10670
> Change-Id: Ifb653ada003719faff261b6e5b2169db37cffdaf
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2282522
> Reviewed-by: Tamer Tas <tmrts@chromium.org>
> Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#68909}
Bug: v8:10670
Change-Id: I4903b3eb8ff39a76594324076f0840b06290044e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2307229
Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: Tamer Tas <tmrts@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70938}
This commit is contained in:
parent
efff3d18ec
commit
91ace57f43
1
.gitignore
vendored
1
.gitignore
vendored
@ -29,6 +29,7 @@
|
||||
.cproject
|
||||
.gclient_entries
|
||||
.gdb_history
|
||||
.jslint-cache
|
||||
.landmines
|
||||
.project
|
||||
.pydevproject
|
||||
|
10
PRESUBMIT.py
10
PRESUBMIT.py
@ -80,6 +80,7 @@ def _V8PresubmitChecks(input_api, output_api):
|
||||
sys.path.append(input_api.os_path.join(
|
||||
input_api.PresubmitLocalPath(), 'tools'))
|
||||
from v8_presubmit import CppLintProcessor
|
||||
from v8_presubmit import JSLintProcessor
|
||||
from v8_presubmit import TorqueLintProcessor
|
||||
from v8_presubmit import SourceProcessor
|
||||
from v8_presubmit import StatusFilesProcessor
|
||||
@ -95,6 +96,11 @@ def _V8PresubmitChecks(input_api, output_api):
|
||||
affected_file,
|
||||
files_to_check=(r'.+\.tq'))
|
||||
|
||||
def FilterJSFile(affected_file):
|
||||
return input_api.FilterSourceFile(
|
||||
affected_file,
|
||||
white_list=(r'.+\.m?js'))
|
||||
|
||||
results = []
|
||||
if not CppLintProcessor().RunOnFiles(
|
||||
input_api.AffectedFiles(file_filter=FilterFile, include_deletes=False)):
|
||||
@ -103,6 +109,10 @@ def _V8PresubmitChecks(input_api, output_api):
|
||||
input_api.AffectedFiles(file_filter=FilterTorqueFile,
|
||||
include_deletes=False)):
|
||||
results.append(output_api.PresubmitError("Torque format check failed"))
|
||||
if not JSLintProcessor().RunOnFiles(
|
||||
input_api.AffectedFiles(file_filter=FilterJSFile,
|
||||
include_deletes=False)):
|
||||
results.append(output_api.PresubmitError("JS format check failed"))
|
||||
if not SourceProcessor().RunOnFiles(
|
||||
input_api.AffectedFiles(include_deletes=False)):
|
||||
results.append(output_api.PresubmitError(
|
||||
|
@ -3,7 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
class State {
|
||||
_timeSelection = { start: 0, end: Infinity };
|
||||
_timeSelection = {start: 0, end: Infinity};
|
||||
_map;
|
||||
_ic;
|
||||
_selectedMapLogEntries;
|
||||
@ -60,11 +60,11 @@ class State {
|
||||
this._deoptTimeline = timeline;
|
||||
}
|
||||
set chunks(value) {
|
||||
//TODO(zcankara) split up between maps and ics, and every timeline track
|
||||
// TODO(zcankara) split up between maps and ics, and every timeline track
|
||||
this._chunks = value;
|
||||
}
|
||||
get chunks() {
|
||||
//TODO(zcankara) split up between maps and ics, and every timeline track
|
||||
// TODO(zcankara) split up between maps and ics, and every timeline track
|
||||
return this._chunks;
|
||||
}
|
||||
get nofChunks() {
|
||||
@ -74,20 +74,20 @@ class State {
|
||||
this._nofChunks = count;
|
||||
}
|
||||
get map() {
|
||||
//TODO(zcankara) rename as selectedMapEvents, array of selected events
|
||||
// TODO(zcankara) rename as selectedMapEvents, array of selected events
|
||||
return this._map;
|
||||
}
|
||||
set map(value) {
|
||||
//TODO(zcankara) rename as selectedMapEvents, array of selected events
|
||||
// TODO(zcankara) rename as selectedMapEvents, array of selected events
|
||||
if (!value) return;
|
||||
this._map = value;
|
||||
}
|
||||
get ic() {
|
||||
//TODO(zcankara) rename selectedICEvents, array of selected events
|
||||
// TODO(zcankara) rename selectedICEvents, array of selected events
|
||||
return this._ic;
|
||||
}
|
||||
set ic(value) {
|
||||
//TODO(zcankara) rename selectedIcEvents, array of selected events
|
||||
// TODO(zcankara) rename selectedIcEvents, array of selected events
|
||||
if (!value) return;
|
||||
this._ic = value;
|
||||
}
|
||||
@ -122,4 +122,4 @@ class State {
|
||||
}
|
||||
}
|
||||
|
||||
export { State };
|
||||
export {State};
|
||||
|
@ -4,44 +4,48 @@
|
||||
|
||||
class SelectionEvent extends CustomEvent {
|
||||
// TODO: turn into static class fields once Safari supports it.
|
||||
static get name() { return "showentries"; }
|
||||
static get name() {
|
||||
return 'showentries';
|
||||
}
|
||||
constructor(entries) {
|
||||
super(SelectionEvent.name, { bubbles: true, composed: true });
|
||||
super(SelectionEvent.name, {bubbles: true, composed: true});
|
||||
if (!Array.isArray(entries) || entries.length == 0) {
|
||||
throw new Error("No valid entries selected!");
|
||||
throw new Error('No valid entries selected!');
|
||||
}
|
||||
this.entries = entries;
|
||||
}
|
||||
}
|
||||
|
||||
class FocusEvent extends CustomEvent {
|
||||
static get name() { return "showentrydetail"; }
|
||||
static get name() {
|
||||
return 'showentrydetail';
|
||||
}
|
||||
constructor(entry) {
|
||||
super(FocusEvent.name, { bubbles: true, composed: true });
|
||||
super(FocusEvent.name, {bubbles: true, composed: true});
|
||||
this.entry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
class SelectTimeEvent extends CustomEvent {
|
||||
static get name() { return 'timerangeselect'; }
|
||||
static get name() {
|
||||
return 'timerangeselect';
|
||||
}
|
||||
constructor(start, end) {
|
||||
super(SelectTimeEvent.name, { bubbles: true, composed: true });
|
||||
super(SelectTimeEvent.name, {bubbles: true, composed: true});
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
class SynchronizeSelectionEvent extends CustomEvent {
|
||||
static get name() { return 'syncselection'; }
|
||||
static get name() {
|
||||
return 'syncselection';
|
||||
}
|
||||
constructor(start, end) {
|
||||
super(SynchronizeSelectionEvent.name, { bubbles: true, composed: true });
|
||||
super(SynchronizeSelectionEvent.name, {bubbles: true, composed: true});
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
SelectionEvent, FocusEvent, SelectTimeEvent,
|
||||
SynchronizeSelectionEvent
|
||||
};
|
||||
export {SelectionEvent, FocusEvent, SelectTimeEvent, SynchronizeSelectionEvent};
|
||||
|
@ -144,7 +144,7 @@ class DOM {
|
||||
if (className) node.classList.add(className);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
static tr(className) {
|
||||
const node = document.createElement('tr');
|
||||
if (className) node.classList.add(className);
|
||||
@ -162,12 +162,13 @@ class DOM {
|
||||
}
|
||||
|
||||
static defineCustomElement(path, generator) {
|
||||
let name = path.substring(path.lastIndexOf("/") + 1, path.length);
|
||||
let name = path.substring(path.lastIndexOf('/') + 1, path.length);
|
||||
path = path + '-template.html';
|
||||
fetch(path)
|
||||
.then(stream => stream.text())
|
||||
.then(
|
||||
templateText => customElements.define(name, generator(templateText)));
|
||||
.then(stream => stream.text())
|
||||
.then(
|
||||
templateText =>
|
||||
customElements.define(name, generator(templateText)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,7 +179,7 @@ function $(id) {
|
||||
class V8CustomElement extends HTMLElement {
|
||||
constructor(templateText) {
|
||||
super();
|
||||
const shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
const shadowRoot = this.attachShadow({mode: 'open'});
|
||||
shadowRoot.innerHTML = templateText;
|
||||
}
|
||||
$(id) {
|
||||
@ -190,7 +191,6 @@ class V8CustomElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LazyTable {
|
||||
constructor(table, rowData, rowElementCreator) {
|
||||
this._table = table;
|
||||
@ -198,7 +198,7 @@ class LazyTable {
|
||||
this._rowElementCreator = rowElementCreator;
|
||||
const tbody = table.querySelector('tbody');
|
||||
table.replaceChild(document.createElement('tbody'), tbody);
|
||||
table.querySelector("tfoot td").onclick = (e) => this._addMoreRows();
|
||||
table.querySelector('tfoot td').onclick = (e) => this._addMoreRows();
|
||||
this._addMoreRows();
|
||||
}
|
||||
|
||||
@ -216,7 +216,6 @@ class LazyTable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LazyTable {
|
||||
constructor(table, rowData, rowElementCreator) {
|
||||
this._table = table;
|
||||
@ -224,7 +223,7 @@ class LazyTable {
|
||||
this._rowElementCreator = rowElementCreator;
|
||||
const tbody = table.querySelector('tbody');
|
||||
table.replaceChild(document.createElement('tbody'), tbody);
|
||||
table.querySelector("tfoot td").onclick = (e) => this._addMoreRows();
|
||||
table.querySelector('tfoot td').onclick = (e) => this._addMoreRows();
|
||||
this._addMoreRows();
|
||||
}
|
||||
|
||||
@ -247,6 +246,12 @@ function delay(time) {
|
||||
}
|
||||
|
||||
export {
|
||||
DOM, $, V8CustomElement, formatBytes,
|
||||
typeToColor, CSSColor, delay, LazyTable,
|
||||
DOM,
|
||||
$,
|
||||
V8CustomElement,
|
||||
formatBytes,
|
||||
typeToColor,
|
||||
CSSColor,
|
||||
delay,
|
||||
LazyTable,
|
||||
};
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import { IcLogEntry } from "./log/ic.mjs";
|
||||
import {IcLogEntry} from './log/ic.mjs';
|
||||
|
||||
// For compatibility with console scripts:
|
||||
print = console.log;
|
||||
@ -51,8 +51,7 @@ export class Group {
|
||||
group.percentage = Math.round(group.count / length * 100 * 100) / 100;
|
||||
result.push(group);
|
||||
}
|
||||
result.sort((a, b) => { return b.count - a.count });
|
||||
result.sort((a, b) => {return b.count - a.count});
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,194 +2,194 @@
|
||||
// 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 { MapLogEntry } from "./log/map.mjs";
|
||||
import { FocusEvent, SelectTimeEvent, SelectionEvent } from './events.mjs';
|
||||
import { DOM, V8CustomElement, delay } from './helper.mjs';
|
||||
import { IcLogEntry } from './log/ic.mjs';
|
||||
import {FocusEvent, SelectionEvent, SelectTimeEvent} from './events.mjs';
|
||||
import {delay, DOM, V8CustomElement} from './helper.mjs';
|
||||
import {Group} from './ic-model.mjs';
|
||||
import {IcLogEntry} from './log/ic.mjs';
|
||||
import {MapLogEntry} from './log/map.mjs';
|
||||
|
||||
DOM.defineCustomElement('ic-panel', (templateText) =>
|
||||
class ICPanel extends V8CustomElement {
|
||||
_selectedLogEntries;
|
||||
_timeline;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.initGroupKeySelect();
|
||||
this.groupKey.addEventListener(
|
||||
'change', e => this.updateTable(e));
|
||||
}
|
||||
set timeline(value) {
|
||||
console.assert(value !== undefined, "timeline undefined!");
|
||||
this._timeline = value;
|
||||
this.selectedLogEntries = this._timeline.all;
|
||||
this.updateCount();
|
||||
}
|
||||
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 selectedLogEntries(value) {
|
||||
this._selectedLogEntries = value;
|
||||
this.update();
|
||||
}
|
||||
|
||||
async update() {
|
||||
await delay(1);
|
||||
this.updateCount();
|
||||
this.updateTable();
|
||||
}
|
||||
|
||||
updateCount() {
|
||||
this.count.innerHTML = "length=" + this._selectedLogEntries.length;
|
||||
}
|
||||
|
||||
updateTable(event) {
|
||||
let select = this.groupKey;
|
||||
let key = select.options[select.selectedIndex].text;
|
||||
let tableBody = this.tableBody;
|
||||
DOM.removeAllChildren(tableBody);
|
||||
let groups = Group.groupBy(this._selectedLogEntries, key, true);
|
||||
this.render(groups, tableBody);
|
||||
}
|
||||
|
||||
escapeHtml(unsafe) {
|
||||
if (!unsafe) return "";
|
||||
return unsafe.toString()
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
handleMapClick(e) {
|
||||
const group = e.target.parentNode.entry;
|
||||
const id = group.key;
|
||||
const selectedMapLogEntries =
|
||||
this.searchIcLogEntryToMapLogEntry(id, group.entries);
|
||||
this.dispatchEvent(new SelectionEvent(selectedMapLogEntries));
|
||||
}
|
||||
|
||||
searchIcLogEntryToMapLogEntry(id, icLogEntries) {
|
||||
// searches for mapLogEntries using the id, time
|
||||
const selectedMapLogEntriesSet = new Set();
|
||||
for (const icLogEntry of icLogEntries) {
|
||||
const time = icLogEntry.time;
|
||||
const selectedMap = MapLogEntry.get(id, time);
|
||||
selectedMapLogEntriesSet.add(selectedMap);
|
||||
DOM.defineCustomElement(
|
||||
'ic-panel', (templateText) => class ICPanel extends V8CustomElement {
|
||||
_selectedLogEntries;
|
||||
_timeline;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.initGroupKeySelect();
|
||||
this.groupKey.addEventListener('change', e => this.updateTable(e));
|
||||
}
|
||||
set timeline(value) {
|
||||
console.assert(value !== undefined, 'timeline undefined!');
|
||||
this._timeline = value;
|
||||
this.selectedLogEntries = this._timeline.all;
|
||||
this.updateCount();
|
||||
}
|
||||
get groupKey() {
|
||||
return this.$('#group-key');
|
||||
}
|
||||
return Array.from(selectedMapLogEntriesSet);
|
||||
}
|
||||
|
||||
//TODO(zcankara) Handle in the processor for events with source positions.
|
||||
handleFilePositionClick(e) {
|
||||
const tr = e.target.parentNode;
|
||||
const sourcePosition = tr.group.entries[0].sourcePosition;
|
||||
this.dispatchEvent(new FocusEvent(sourcePosition));
|
||||
}
|
||||
get table() {
|
||||
return this.$('#table');
|
||||
}
|
||||
|
||||
render(groups, parent) {
|
||||
const fragment = document.createDocumentFragment();
|
||||
const max = Math.min(1000, groups.length)
|
||||
const detailsClickHandler = this.handleDetailsClick.bind(this);
|
||||
const mapClickHandler = this.handleMapClick.bind(this);
|
||||
const fileClickHandler = this.handleFilePositionClick.bind(this);
|
||||
for (let i = 0; i < max; i++) {
|
||||
const group = groups[i];
|
||||
const tr = DOM.tr();
|
||||
tr.group = group;
|
||||
const details = tr.appendChild(DOM.td('', 'toggle'));
|
||||
details.onclick = detailsClickHandler;
|
||||
tr.appendChild(DOM.td(group.percentage + "%", 'percentage'));
|
||||
tr.appendChild(DOM.td(group.count, 'count'));
|
||||
const valueTd = tr.appendChild(DOM.td(group.key, 'key'));
|
||||
if (group.property === "map") {
|
||||
valueTd.onclick = mapClickHandler;
|
||||
valueTd.classList.add('clickable');
|
||||
} else if (group.property == "filePosition") {
|
||||
valueTd.classList.add('clickable');
|
||||
valueTd.onclick = fileClickHandler;
|
||||
get tableBody() {
|
||||
return this.$('#table-body');
|
||||
}
|
||||
|
||||
get count() {
|
||||
return this.$('#count');
|
||||
}
|
||||
|
||||
get spanSelectAll() {
|
||||
return this.querySelectorAll('span');
|
||||
}
|
||||
|
||||
set selectedLogEntries(value) {
|
||||
this._selectedLogEntries = value;
|
||||
this.update();
|
||||
}
|
||||
|
||||
async update() {
|
||||
await delay(1);
|
||||
this.updateCount();
|
||||
this.updateTable();
|
||||
}
|
||||
|
||||
updateCount() {
|
||||
this.count.innerHTML = 'length=' + this._selectedLogEntries.length;
|
||||
}
|
||||
|
||||
updateTable(event) {
|
||||
let select = this.groupKey;
|
||||
let key = select.options[select.selectedIndex].text;
|
||||
let tableBody = this.tableBody;
|
||||
DOM.removeAllChildren(tableBody);
|
||||
let groups = Group.groupBy(this._selectedLogEntries, key, true);
|
||||
this.render(groups, tableBody);
|
||||
}
|
||||
|
||||
escapeHtml(unsafe) {
|
||||
if (!unsafe) return '';
|
||||
return unsafe.toString()
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
handleMapClick(e) {
|
||||
const group = e.target.parentNode.entry;
|
||||
const id = group.key;
|
||||
const selectedMapLogEntries =
|
||||
this.searchIcLogEntryToMapLogEntry(id, group.entries);
|
||||
this.dispatchEvent(new SelectionEvent(selectedMapLogEntries));
|
||||
}
|
||||
|
||||
searchIcLogEntryToMapLogEntry(id, icLogEntries) {
|
||||
// searches for mapLogEntries using the id, time
|
||||
const selectedMapLogEntriesSet = new Set();
|
||||
for (const icLogEntry of icLogEntries) {
|
||||
const time = icLogEntry.time;
|
||||
const selectedMap = MapLogEntry.get(id, time);
|
||||
selectedMapLogEntriesSet.add(selectedMap);
|
||||
}
|
||||
fragment.appendChild(tr);
|
||||
return Array.from(selectedMapLogEntriesSet);
|
||||
}
|
||||
const omitted = groups.length - max;
|
||||
if (omitted > 0) {
|
||||
const tr = DOM.tr();
|
||||
const tdNode =
|
||||
tr.appendChild(DOM.td('Omitted ' + omitted + " entries."));
|
||||
tdNode.colSpan = 4;
|
||||
fragment.appendChild(tr);
|
||||
}
|
||||
parent.appendChild(fragment);
|
||||
}
|
||||
|
||||
handleDetailsClick(event) {
|
||||
const tr = event.target.parentNode;
|
||||
const group = tr.group;
|
||||
// Create subgroup in-place if the don't exist yet.
|
||||
if (group.groups === undefined) {
|
||||
group.createSubGroups();
|
||||
this.renderDrilldown(group, tr);
|
||||
// TODO(zcankara) Handle in the processor for events with source
|
||||
// positions.
|
||||
handleFilePositionClick(e) {
|
||||
const tr = e.target.parentNode;
|
||||
const sourcePosition = tr.group.entries[0].sourcePosition;
|
||||
this.dispatchEvent(new FocusEvent(sourcePosition));
|
||||
}
|
||||
let 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(group, previousSibling) {
|
||||
let tr = DOM.tr("entry-details");
|
||||
tr.style.display = "none";
|
||||
// indent by one td.
|
||||
tr.appendChild(DOM.td());
|
||||
let td = DOM.td();
|
||||
td.colSpan = 3;
|
||||
for (let key in group.groups) {
|
||||
this.renderDrilldownGroup(td, group.groups[key], key);
|
||||
render(groups, parent) {
|
||||
const fragment = document.createDocumentFragment();
|
||||
const max = Math.min(1000, groups.length)
|
||||
const detailsClickHandler = this.handleDetailsClick.bind(this);
|
||||
const mapClickHandler = this.handleMapClick.bind(this);
|
||||
const fileClickHandler = this.handleFilePositionClick.bind(this);
|
||||
for (let i = 0; i < max; i++) {
|
||||
const group = groups[i];
|
||||
const tr = DOM.tr();
|
||||
tr.group = group;
|
||||
const details = tr.appendChild(DOM.td('', 'toggle'));
|
||||
details.onclick = detailsClickHandler;
|
||||
tr.appendChild(DOM.td(group.percentage + '%', 'percentage'));
|
||||
tr.appendChild(DOM.td(group.count, 'count'));
|
||||
const valueTd = tr.appendChild(DOM.td(group.key, 'key'));
|
||||
if (group.property === 'map') {
|
||||
valueTd.onclick = mapClickHandler;
|
||||
valueTd.classList.add('clickable');
|
||||
} else if (group.property == 'filePosition') {
|
||||
valueTd.classList.add('clickable');
|
||||
valueTd.onclick = fileClickHandler;
|
||||
}
|
||||
fragment.appendChild(tr);
|
||||
}
|
||||
const omitted = groups.length - max;
|
||||
if (omitted > 0) {
|
||||
const tr = DOM.tr();
|
||||
const tdNode =
|
||||
tr.appendChild(DOM.td('Omitted ' + omitted + ' entries.'));
|
||||
tdNode.colSpan = 4;
|
||||
fragment.appendChild(tr);
|
||||
}
|
||||
parent.appendChild(fragment);
|
||||
}
|
||||
tr.appendChild(td);
|
||||
// Append the new TR after previousSibling.
|
||||
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling)
|
||||
}
|
||||
|
||||
renderDrilldownGroup(td, children, key) {
|
||||
const max = 20;
|
||||
const div = DOM.div('drilldown-group-title');
|
||||
div.textContent =
|
||||
`Grouped by ${key} [top ${max} out of ${children.length}]`;
|
||||
td.appendChild(div);
|
||||
const table = DOM.table();
|
||||
this.render(children.slice(0, max), table, false)
|
||||
td.appendChild(table);
|
||||
}
|
||||
|
||||
initGroupKeySelect() {
|
||||
const select = this.groupKey;
|
||||
select.options.length = 0;
|
||||
for (const propertyName of IcLogEntry.propertyNames) {
|
||||
const option = document.createElement("option");
|
||||
option.text = propertyName;
|
||||
select.add(option);
|
||||
handleDetailsClick(event) {
|
||||
const tr = event.target.parentNode;
|
||||
const group = tr.group;
|
||||
// Create subgroup in-place if the don't exist yet.
|
||||
if (group.groups === undefined) {
|
||||
group.createSubGroups();
|
||||
this.renderDrilldown(group, tr);
|
||||
}
|
||||
let 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(group, previousSibling) {
|
||||
let tr = DOM.tr('entry-details');
|
||||
tr.style.display = 'none';
|
||||
// indent by one td.
|
||||
tr.appendChild(DOM.td());
|
||||
let td = DOM.td();
|
||||
td.colSpan = 3;
|
||||
for (let key in group.groups) {
|
||||
this.renderDrilldownGroup(td, group.groups[key], key);
|
||||
}
|
||||
tr.appendChild(td);
|
||||
// Append the new TR after previousSibling.
|
||||
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling)
|
||||
}
|
||||
|
||||
renderDrilldownGroup(td, children, key) {
|
||||
const max = 20;
|
||||
const div = DOM.div('drilldown-group-title');
|
||||
div.textContent =
|
||||
`Grouped by ${key} [top ${max} out of ${children.length}]`;
|
||||
td.appendChild(div);
|
||||
const table = DOM.table();
|
||||
this.render(children.slice(0, max), table, false)
|
||||
td.appendChild(table);
|
||||
}
|
||||
|
||||
initGroupKeySelect() {
|
||||
const select = this.groupKey;
|
||||
select.options.length = 0;
|
||||
for (const propertyName of IcLogEntry.propertyNames) {
|
||||
const option = document.createElement('option');
|
||||
option.text = propertyName;
|
||||
select.add(option);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -2,22 +2,23 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import { SelectionEvent, FocusEvent, SelectTimeEvent } from "./events.mjs";
|
||||
import { State } from "./app-model.mjs";
|
||||
import { MapLogEntry } from "./log/map.mjs";
|
||||
import { IcLogEntry } from "./log/ic.mjs";
|
||||
import { Processor } from "./processor.mjs";
|
||||
import { SourcePosition } from "../profile.mjs";
|
||||
import { $ } from "./helper.mjs";
|
||||
import {SourcePosition} from '../profile.mjs';
|
||||
|
||||
import {State} from './app-model.mjs';
|
||||
import {FocusEvent, SelectionEvent, SelectTimeEvent} from './events.mjs';
|
||||
import {$} from './helper.mjs';
|
||||
import {IcLogEntry} from './log/ic.mjs';
|
||||
import {MapLogEntry} from './log/map.mjs';
|
||||
import {Processor} from './processor.mjs';
|
||||
|
||||
class App {
|
||||
_state;
|
||||
_view;
|
||||
_navigation;
|
||||
_startupPromise;
|
||||
constructor(fileReaderId, mapPanelId, mapStatsPanelId, timelinePanelId,
|
||||
icPanelId, mapTrackId, icTrackId, deoptTrackId, sourcePanelId) {
|
||||
constructor(
|
||||
fileReaderId, mapPanelId, mapStatsPanelId, timelinePanelId, icPanelId,
|
||||
mapTrackId, icTrackId, deoptTrackId, sourcePanelId) {
|
||||
this._view = {
|
||||
__proto__: null,
|
||||
logFileReader: $(fileReaderId),
|
||||
@ -31,32 +32,30 @@ class App {
|
||||
sourcePanel: $(sourcePanelId)
|
||||
};
|
||||
this.toggleSwitch = $('.theme-switch input[type="checkbox"]');
|
||||
this.toggleSwitch.addEventListener("change", (e) => this.switchTheme(e));
|
||||
this._view.logFileReader.addEventListener("fileuploadstart", (e) =>
|
||||
this.handleFileUploadStart(e)
|
||||
);
|
||||
this._view.logFileReader.addEventListener("fileuploadend", (e) =>
|
||||
this.handleFileUploadEnd(e)
|
||||
);
|
||||
this.toggleSwitch.addEventListener('change', (e) => this.switchTheme(e));
|
||||
this._view.logFileReader.addEventListener(
|
||||
'fileuploadstart', (e) => this.handleFileUploadStart(e));
|
||||
this._view.logFileReader.addEventListener(
|
||||
'fileuploadend', (e) => this.handleFileUploadEnd(e));
|
||||
this._startupPromise = this.runAsyncInitialize();
|
||||
}
|
||||
|
||||
async runAsyncInitialize() {
|
||||
await Promise.all([
|
||||
import("./ic-panel.mjs"),
|
||||
import("./timeline-panel.mjs"),
|
||||
import("./stats-panel.mjs"),
|
||||
import("./map-panel.mjs"),
|
||||
import("./source-panel.mjs"),
|
||||
]);
|
||||
document.addEventListener('keydown',
|
||||
e => this._navigation?.handleKeyDown(e));
|
||||
document.addEventListener(SelectionEvent.name,
|
||||
e => this.handleShowEntries(e));
|
||||
document.addEventListener(FocusEvent.name,
|
||||
e => this.handleShowEntryDetail(e));
|
||||
document.addEventListener(SelectTimeEvent.name,
|
||||
e => this.handleTimeRangeSelect(e));
|
||||
import('./ic-panel.mjs'),
|
||||
import('./timeline-panel.mjs'),
|
||||
import('./stats-panel.mjs'),
|
||||
import('./map-panel.mjs'),
|
||||
import('./source-panel.mjs'),
|
||||
]);
|
||||
document.addEventListener(
|
||||
'keydown', e => this._navigation?.handleKeyDown(e));
|
||||
document.addEventListener(
|
||||
SelectionEvent.name, e => this.handleShowEntries(e));
|
||||
document.addEventListener(
|
||||
FocusEvent.name, e => this.handleShowEntryDetail(e));
|
||||
document.addEventListener(
|
||||
SelectTimeEvent.name, e => this.handleTimeRangeSelect(e));
|
||||
}
|
||||
|
||||
handleShowEntries(e) {
|
||||
@ -67,7 +66,7 @@ class App {
|
||||
} else if (e.entries[0] instanceof SourcePosition) {
|
||||
this.showSourcePositionEntries(e.entries);
|
||||
} else {
|
||||
throw new Error("Unknown selection type!");
|
||||
throw new Error('Unknown selection type!');
|
||||
}
|
||||
e.stopPropagation();
|
||||
}
|
||||
@ -84,7 +83,7 @@ class App {
|
||||
this._state.selectedDeoptLogEntries = entries;
|
||||
}
|
||||
showSourcePositionEntries(entries) {
|
||||
//TODO: Handle multiple source position selection events
|
||||
// TODO: Handle multiple source position selection events
|
||||
this._view.sourcePanel.selectedSourcePositions = entries
|
||||
}
|
||||
|
||||
@ -98,7 +97,7 @@ class App {
|
||||
this.showMapEntries(this._state.mapTimeline.selection);
|
||||
this.showIcEntries(this._state.icTimeline.selection);
|
||||
this.showDeoptEntries(this._state.deoptTimeline.selection);
|
||||
this._view.timelinePanel.timeSelection = {start,end};
|
||||
this._view.timelinePanel.timeSelection = {start, end};
|
||||
}
|
||||
|
||||
handleShowEntryDetail(e) {
|
||||
@ -109,7 +108,7 @@ class App {
|
||||
} else if (e.entry instanceof SourcePosition) {
|
||||
this.selectSourcePosition(e.entry);
|
||||
} else {
|
||||
throw new Error("Unknown selection type!");
|
||||
throw new Error('Unknown selection type!');
|
||||
}
|
||||
e.stopPropagation();
|
||||
}
|
||||
@ -129,7 +128,7 @@ class App {
|
||||
|
||||
handleFileUploadStart(e) {
|
||||
this.restartApp();
|
||||
$("#container").className = "initial";
|
||||
$('#container').className = 'initial';
|
||||
}
|
||||
|
||||
restartApp() {
|
||||
@ -150,17 +149,18 @@ class App {
|
||||
// Transitions must be set before timeline for stats panel.
|
||||
this._view.mapPanel.timeline = mapTimeline;
|
||||
this._view.mapTrack.data = mapTimeline;
|
||||
this._view.mapStatsPanel.transitions = this._state.mapTimeline.transitions;
|
||||
this._view.mapStatsPanel.transitions =
|
||||
this._state.mapTimeline.transitions;
|
||||
this._view.mapStatsPanel.timeline = mapTimeline;
|
||||
this._view.icPanel.timeline = icTimeline;
|
||||
this._view.icTrack.data = icTimeline;
|
||||
this._view.deoptTrack.data = deoptTimeline;
|
||||
this._view.sourcePanel.data = processor.scripts
|
||||
} catch(e) {
|
||||
this._view.logFileReader.error = "Log file contains errors!"
|
||||
throw(e);
|
||||
} catch (e) {
|
||||
this._view.logFileReader.error = 'Log file contains errors!'
|
||||
throw (e);
|
||||
} finally {
|
||||
$("#container").className = "loaded";
|
||||
$('#container').className = 'loaded';
|
||||
this.fileLoaded = true;
|
||||
}
|
||||
}
|
||||
@ -172,9 +172,8 @@ class App {
|
||||
}
|
||||
|
||||
switchTheme(event) {
|
||||
document.documentElement.dataset.theme = event.target.checked
|
||||
? "light"
|
||||
: "dark";
|
||||
document.documentElement.dataset.theme =
|
||||
event.target.checked ? 'light' : 'dark';
|
||||
if (this.fileLoaded) {
|
||||
this.refreshTimelineTrackView();
|
||||
}
|
||||
@ -268,7 +267,7 @@ class Navigation {
|
||||
}
|
||||
handleKeyDown(event) {
|
||||
switch (event.key) {
|
||||
case "ArrowUp":
|
||||
case 'ArrowUp':
|
||||
event.preventDefault();
|
||||
if (event.shiftKey) {
|
||||
this.selectPrevEdge();
|
||||
@ -276,7 +275,7 @@ class Navigation {
|
||||
this.moveInChunk(-1);
|
||||
}
|
||||
return false;
|
||||
case "ArrowDown":
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
if (event.shiftKey) {
|
||||
this.selectNextEdge();
|
||||
@ -284,20 +283,20 @@ class Navigation {
|
||||
this.moveInChunk(1);
|
||||
}
|
||||
return false;
|
||||
case "ArrowLeft":
|
||||
case 'ArrowLeft':
|
||||
this.moveInChunks(false);
|
||||
break;
|
||||
case "ArrowRight":
|
||||
case 'ArrowRight':
|
||||
this.moveInChunks(true);
|
||||
break;
|
||||
case "+":
|
||||
case '+':
|
||||
this.increaseTimelineResolution();
|
||||
break;
|
||||
case "-":
|
||||
case '-':
|
||||
this.decreaseTimelineResolution();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { App };
|
||||
export {App};
|
||||
|
@ -1,82 +1,84 @@
|
||||
// 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 { DOM, V8CustomElement } from './helper.mjs';
|
||||
import {DOM, V8CustomElement} from './helper.mjs';
|
||||
|
||||
DOM.defineCustomElement('log-file-reader', (templateText) =>
|
||||
class LogFileReader extends V8CustomElement {
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.addEventListener('click', e => this.handleClick(e));
|
||||
this.addEventListener('dragover', e => this.handleDragOver(e));
|
||||
this.addEventListener('drop', e => this.handleChange(e));
|
||||
this.$('#file').addEventListener('change', e => this.handleChange(e));
|
||||
this.$('#fileReader').addEventListener('keydown',
|
||||
e => this.handleKeyEvent(e));
|
||||
DOM.defineCustomElement('log-file-reader',
|
||||
(templateText) =>
|
||||
class LogFileReader extends V8CustomElement {
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.addEventListener('click', e => this.handleClick(e));
|
||||
this.addEventListener('dragover', e => this.handleDragOver(e));
|
||||
this.addEventListener('drop', e => this.handleChange(e));
|
||||
this.$('#file').addEventListener('change', e => this.handleChange(e));
|
||||
this.$('#fileReader')
|
||||
.addEventListener('keydown', e => this.handleKeyEvent(e));
|
||||
}
|
||||
|
||||
set error(message) {
|
||||
this.updateLabel(message);
|
||||
this.root.className = 'fail';
|
||||
}
|
||||
|
||||
updateLabel(text) {
|
||||
this.$('#label').innerText = text;
|
||||
}
|
||||
|
||||
handleKeyEvent(event) {
|
||||
if (event.key == 'Enter') this.handleClick(event);
|
||||
}
|
||||
|
||||
handleClick(event) {
|
||||
this.$('#file').click();
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
// Used for drop and file change.
|
||||
event.preventDefault();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('fileuploadstart', {bubbles: true, composed: true}));
|
||||
var host = event.dataTransfer ? event.dataTransfer : event.target;
|
||||
this.readFile(host.files[0]);
|
||||
}
|
||||
|
||||
handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.fileReader.focus();
|
||||
}
|
||||
|
||||
get fileReader() {
|
||||
return this.$('#fileReader');
|
||||
}
|
||||
|
||||
get root() {
|
||||
return this.$('#root');
|
||||
}
|
||||
|
||||
readFile(file) {
|
||||
if (!file) {
|
||||
this.error = 'Failed to load file.';
|
||||
return;
|
||||
}
|
||||
this.fileReader.blur();
|
||||
this.root.className = 'loading';
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => this.handleFileLoad(e, file);
|
||||
// Delay the loading a bit to allow for CSS animations to happen.
|
||||
setTimeout(() => reader.readAsText(file), 0);
|
||||
}
|
||||
|
||||
set error(message) {
|
||||
this.updateLabel(message);
|
||||
this.root.className = 'fail';
|
||||
}
|
||||
|
||||
updateLabel(text) {
|
||||
this.$('#label').innerText = text;
|
||||
}
|
||||
|
||||
handleKeyEvent(event) {
|
||||
if (event.key == "Enter") this.handleClick(event);
|
||||
}
|
||||
|
||||
handleClick(event) {
|
||||
this.$('#file').click();
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
// Used for drop and file change.
|
||||
event.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'fileuploadstart', { bubbles: true, composed: true }));
|
||||
var host = event.dataTransfer ? event.dataTransfer : event.target;
|
||||
this.readFile(host.files[0]);
|
||||
}
|
||||
|
||||
handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.fileReader.focus();
|
||||
}
|
||||
|
||||
get fileReader() {
|
||||
return this.$('#fileReader');
|
||||
}
|
||||
|
||||
get root() { return this.$("#root"); }
|
||||
|
||||
readFile(file) {
|
||||
if (!file) {
|
||||
this.error = 'Failed to load file.';
|
||||
return;
|
||||
}
|
||||
this.fileReader.blur();
|
||||
this.root.className = 'loading';
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => this.handleFileLoad(e, file);
|
||||
// Delay the loading a bit to allow for CSS animations to happen.
|
||||
setTimeout(() => reader.readAsText(file), 0);
|
||||
}
|
||||
|
||||
handleFileLoad(e, file) {
|
||||
const chunk = e.target.result;
|
||||
this.updateLabel('Finished loading \'' + file.name + '\'.');
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'fileuploadend', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: chunk,
|
||||
}));
|
||||
this.root.className = 'done';
|
||||
}
|
||||
});
|
||||
handleFileLoad(e, file) {
|
||||
const chunk = e.target.result;
|
||||
this.updateLabel('Finished loading \'' + file.name + '\'.');
|
||||
this.dispatchEvent(new CustomEvent('fileuploadend', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: chunk,
|
||||
}));
|
||||
this.root.className = 'done';
|
||||
}
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
// 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 { LogEntry } from './log.mjs';
|
||||
import {LogEntry} from './log.mjs';
|
||||
|
||||
export class DeoptLogEntry extends LogEntry {
|
||||
constructor(type, time) {
|
||||
|
@ -1,12 +1,12 @@
|
||||
// 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 { LogEntry } from './log.mjs';
|
||||
import {LogEntry} from './log.mjs';
|
||||
|
||||
export class IcLogEntry extends LogEntry {
|
||||
constructor(
|
||||
type, fn_file, time, line, column, key, oldState, newState, map, reason,
|
||||
script, modifier, additional) {
|
||||
type, fn_file, time, line, column, key, oldState, newState, map, reason,
|
||||
script, modifier, additional) {
|
||||
super(type, time);
|
||||
this.category = 'other';
|
||||
if (this.type.indexOf('Store') !== -1) {
|
||||
@ -55,18 +55,11 @@ export class IcLogEntry extends LogEntry {
|
||||
this.file = parts[offset];
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
static get propertyNames() {
|
||||
return [
|
||||
'type',
|
||||
'category',
|
||||
'functionName',
|
||||
'filePosition',
|
||||
'state',
|
||||
'key',
|
||||
'map',
|
||||
'reason',
|
||||
'file'
|
||||
'type', 'category', 'functionName', 'filePosition', 'state', 'key', 'map',
|
||||
'reason', 'file'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ export class LogEntry {
|
||||
_time;
|
||||
_type;
|
||||
constructor(type, time) {
|
||||
//TODO(zcankara) remove type and add empty getters to override
|
||||
// TODO(zcankara) remove type and add empty getters to override
|
||||
this._time = time;
|
||||
this._type = type;
|
||||
}
|
||||
@ -18,6 +18,6 @@ export class LogEntry {
|
||||
}
|
||||
// Returns an Array of all possible #type values.
|
||||
static get allTypes() {
|
||||
throw new Error("Not implemented.");
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import { LogEntry } from './log.mjs';
|
||||
import {LogEntry} from './log.mjs';
|
||||
|
||||
// ===========================================================================
|
||||
// Map Log Events
|
||||
@ -11,10 +11,10 @@ const kChunkHeight = 200;
|
||||
const kChunkWidth = 10;
|
||||
|
||||
function define(prototype, name, fn) {
|
||||
Object.defineProperty(prototype, name, { value: fn, enumerable: false });
|
||||
Object.defineProperty(prototype, name, {value: fn, enumerable: false});
|
||||
}
|
||||
|
||||
define(Array.prototype, 'max', function (fn) {
|
||||
define(Array.prototype, 'max', function(fn) {
|
||||
if (this.length === 0) return undefined;
|
||||
if (fn === undefined) fn = (each) => each;
|
||||
let max = fn(this[0]);
|
||||
@ -23,10 +23,10 @@ define(Array.prototype, 'max', function (fn) {
|
||||
}
|
||||
return max;
|
||||
})
|
||||
define(Array.prototype, 'first', function () {
|
||||
define(Array.prototype, 'first', function() {
|
||||
return this[0]
|
||||
});
|
||||
define(Array.prototype, 'last', function () {
|
||||
define(Array.prototype, 'last', function() {
|
||||
return this[this.length - 1]
|
||||
});
|
||||
|
||||
@ -282,9 +282,8 @@ class Edge {
|
||||
return this.type + ' ' + this.symbol() + this.name;
|
||||
}
|
||||
return this.type + ' ' + (this.reason ? this.reason : '') + ' ' +
|
||||
(this.name ? this.name : '')
|
||||
(this.name ? this.name : '')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export { MapLogEntry, Edge, kChunkWidth, kChunkHeight };
|
||||
export {MapLogEntry, Edge, kChunkWidth, kChunkHeight};
|
||||
|
@ -1,77 +1,76 @@
|
||||
// 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 "./stats-panel.mjs";
|
||||
import "./map-panel/map-details.mjs";
|
||||
import "./map-panel/map-transitions.mjs";
|
||||
import { FocusEvent } from './events.mjs';
|
||||
import { MapLogEntry } from "./log/map.mjs";
|
||||
import { DOM, V8CustomElement } from './helper.mjs';
|
||||
import './stats-panel.mjs';
|
||||
import './map-panel/map-details.mjs';
|
||||
import './map-panel/map-transitions.mjs';
|
||||
|
||||
DOM.defineCustomElement('map-panel', (templateText) =>
|
||||
class MapPanel extends V8CustomElement {
|
||||
_map;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.searchBarBtn.addEventListener(
|
||||
'click', e => this.handleSearchBar(e));
|
||||
this.addEventListener(
|
||||
FocusEvent.name, e => this.handleUpdateMapDetails(e));
|
||||
}
|
||||
import {FocusEvent} from './events.mjs';
|
||||
import {DOM, V8CustomElement} from './helper.mjs';
|
||||
import {MapLogEntry} from './log/map.mjs';
|
||||
|
||||
handleUpdateMapDetails(e) {
|
||||
if (e.entry instanceof MapLogEntry) {
|
||||
this.mapDetailsPanel.mapDetails = e.entry;
|
||||
}
|
||||
}
|
||||
DOM.defineCustomElement('map-panel',
|
||||
(templateText) =>
|
||||
class MapPanel extends V8CustomElement {
|
||||
_map;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.searchBarBtn.addEventListener('click', e => this.handleSearchBar(e));
|
||||
this.addEventListener(FocusEvent.name, e => this.handleUpdateMapDetails(e));
|
||||
}
|
||||
|
||||
get mapTransitionsPanel() {
|
||||
return this.$('#map-transitions');
|
||||
handleUpdateMapDetails(e) {
|
||||
if (e.entry instanceof MapLogEntry) {
|
||||
this.mapDetailsPanel.mapDetails = e.entry;
|
||||
}
|
||||
}
|
||||
|
||||
get mapDetailsPanel() {
|
||||
return this.$('#map-details');
|
||||
}
|
||||
get mapTransitionsPanel() {
|
||||
return this.$('#map-transitions');
|
||||
}
|
||||
|
||||
get searchBarBtn() {
|
||||
return this.$('#searchBarBtn');
|
||||
}
|
||||
get mapDetailsPanel() {
|
||||
return this.$('#map-details');
|
||||
}
|
||||
|
||||
get searchBar() {
|
||||
return this.$('#searchBar');
|
||||
}
|
||||
get searchBarBtn() {
|
||||
return this.$('#searchBarBtn');
|
||||
}
|
||||
|
||||
get mapDetails() {
|
||||
return this.mapDetailsPanel.mapDetails;
|
||||
}
|
||||
get searchBar() {
|
||||
return this.$('#searchBar');
|
||||
}
|
||||
|
||||
set timeline(timeline) {
|
||||
this._timeline = timeline;
|
||||
}
|
||||
get mapDetails() {
|
||||
return this.mapDetailsPanel.mapDetails;
|
||||
}
|
||||
|
||||
set map(value) {
|
||||
this._map = value;
|
||||
this.mapTransitionsPanel.map = this._map;
|
||||
}
|
||||
set timeline(timeline) {
|
||||
this._timeline = timeline;
|
||||
}
|
||||
|
||||
handleSearchBar(e) {
|
||||
let searchBar = this.$('#searchBarInput');
|
||||
let searchBarInput = searchBar.value;
|
||||
//access the map from model cache
|
||||
let selectedMap = MapLogEntry.get(parseInt(searchBarInput));
|
||||
if (selectedMap) {
|
||||
searchBar.className = "success";
|
||||
} else {
|
||||
searchBar.className = "failure";
|
||||
}
|
||||
this.dispatchEvent(new FocusEvent(selectedMap));
|
||||
}
|
||||
set map(value) {
|
||||
this._map = value;
|
||||
this.mapTransitionsPanel.map = this._map;
|
||||
}
|
||||
|
||||
set selectedMapLogEntries(list) {
|
||||
this.mapTransitionsPanel.selectedMapLogEntries = list;
|
||||
}
|
||||
get selectedMapLogEntries() {
|
||||
return this.mapTransitionsPanel.selectedMapLogEntries;
|
||||
handleSearchBar(e) {
|
||||
let searchBar = this.$('#searchBarInput');
|
||||
let searchBarInput = searchBar.value;
|
||||
// access the map from model cache
|
||||
let selectedMap = MapLogEntry.get(parseInt(searchBarInput));
|
||||
if (selectedMap) {
|
||||
searchBar.className = 'success';
|
||||
} else {
|
||||
searchBar.className = 'failure';
|
||||
}
|
||||
this.dispatchEvent(new FocusEvent(selectedMap));
|
||||
}
|
||||
|
||||
});
|
||||
set selectedMapLogEntries(list) {
|
||||
this.mapTransitionsPanel.selectedMapLogEntries = list;
|
||||
}
|
||||
get selectedMapLogEntries() {
|
||||
return this.mapTransitionsPanel.selectedMapLogEntries;
|
||||
}
|
||||
});
|
||||
|
@ -1,26 +1,24 @@
|
||||
// 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 { V8CustomElement, DOM} from "../helper.mjs";
|
||||
import { FocusEvent } from "../events.mjs";
|
||||
import {FocusEvent} from '../events.mjs';
|
||||
import {DOM, V8CustomElement} from '../helper.mjs';
|
||||
|
||||
DOM.defineCustomElement(
|
||||
"./map-panel/map-details",
|
||||
(templateText) =>
|
||||
class MapDetails extends V8CustomElement {
|
||||
'./map-panel/map-details',
|
||||
(templateText) => class MapDetails extends V8CustomElement {
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this._filePositionNode.addEventListener("click", e =>
|
||||
this.handleFilePositionClick(e)
|
||||
);
|
||||
this._filePositionNode.addEventListener(
|
||||
'click', e => this.handleFilePositionClick(e));
|
||||
this.selectedMap = undefined;
|
||||
}
|
||||
get mapDetails() {
|
||||
return this.$("#mapDetails");
|
||||
return this.$('#mapDetails');
|
||||
}
|
||||
|
||||
get _filePositionNode() {
|
||||
return this.$("#filePositionNode");
|
||||
return this.$('#filePositionNode');
|
||||
}
|
||||
|
||||
setSelectedMap(value) {
|
||||
@ -28,21 +26,20 @@ DOM.defineCustomElement(
|
||||
}
|
||||
|
||||
set mapDetails(map) {
|
||||
let details = "";
|
||||
let clickableDetails = "";
|
||||
let details = '';
|
||||
let clickableDetails = '';
|
||||
if (map) {
|
||||
clickableDetails += "ID: " + map.id;
|
||||
clickableDetails += "\nSource location: " + map.filePosition;
|
||||
details += "\n" + map.description;
|
||||
clickableDetails += 'ID: ' + map.id;
|
||||
clickableDetails += '\nSource location: ' + map.filePosition;
|
||||
details += '\n' + map.description;
|
||||
this.setSelectedMap(map);
|
||||
}
|
||||
this._filePositionNode.innerText = clickableDetails;
|
||||
this._filePositionNode.classList.add("clickable");
|
||||
this._filePositionNode.classList.add('clickable');
|
||||
this.mapDetails.innerText = details;
|
||||
}
|
||||
|
||||
handleFilePositionClick() {
|
||||
this.dispatchEvent(new FocusEvent(this.selectedMap.sourcePosition));
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1,197 +1,188 @@
|
||||
// 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 { V8CustomElement, DOM, typeToColor } from "../helper.mjs";
|
||||
import { FocusEvent, SelectionEvent } from "../events.mjs";
|
||||
import {FocusEvent, SelectionEvent} from '../events.mjs';
|
||||
import {DOM, typeToColor, V8CustomElement} from '../helper.mjs';
|
||||
|
||||
DOM.defineCustomElement(
|
||||
"./map-panel/map-transitions",
|
||||
(templateText) =>
|
||||
class MapTransitions extends V8CustomElement {
|
||||
_map;
|
||||
_selectedMapLogEntries;
|
||||
_displayedMapsInTree;
|
||||
_showMapsUpdateId;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.transitionView.addEventListener("mousemove", (e) =>
|
||||
this.handleTransitionViewChange(e)
|
||||
);
|
||||
this.currentNode = this.transitionView;
|
||||
this.currentMap = undefined;
|
||||
}
|
||||
DOM.defineCustomElement('./map-panel/map-transitions',
|
||||
(templateText) =>
|
||||
class MapTransitions extends V8CustomElement {
|
||||
_map;
|
||||
_selectedMapLogEntries;
|
||||
_displayedMapsInTree;
|
||||
_showMapsUpdateId;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.transitionView.addEventListener(
|
||||
'mousemove', (e) => this.handleTransitionViewChange(e));
|
||||
this.currentNode = this.transitionView;
|
||||
this.currentMap = undefined;
|
||||
}
|
||||
|
||||
get transitionView() {
|
||||
return this.$("#transitionView");
|
||||
}
|
||||
get transitionView() {
|
||||
return this.$('#transitionView');
|
||||
}
|
||||
|
||||
get tooltip() {
|
||||
return this.$("#tooltip");
|
||||
}
|
||||
get tooltip() {
|
||||
return this.$('#tooltip');
|
||||
}
|
||||
|
||||
get tooltipContents() {
|
||||
return this.$("#tooltipContents");
|
||||
}
|
||||
get tooltipContents() {
|
||||
return this.$('#tooltipContents');
|
||||
}
|
||||
|
||||
set map(value) {
|
||||
this._map = value;
|
||||
this.showMap();
|
||||
}
|
||||
set map(value) {
|
||||
this._map = value;
|
||||
this.showMap();
|
||||
}
|
||||
|
||||
handleTransitionViewChange(e) {
|
||||
this.tooltip.style.left = e.pageX + "px";
|
||||
this.tooltip.style.top = e.pageY + "px";
|
||||
let map = e.target.map;
|
||||
if (map) {
|
||||
this.tooltipContents.innerText = map.description;
|
||||
}
|
||||
}
|
||||
|
||||
selectMap(map) {
|
||||
this.dispatchEvent(new SelectionEvent([map]));
|
||||
}
|
||||
|
||||
showMap() {
|
||||
if (this.currentMap === this._map) return;
|
||||
this.currentMap = this._map;
|
||||
this.selectedMapLogEntries = [this._map];
|
||||
this.showMaps();
|
||||
}
|
||||
|
||||
showMaps() {
|
||||
clearTimeout(this._showMapsUpdateId);
|
||||
this._showMapsUpdateId = setTimeout(() => this._showMaps(), 250);
|
||||
}
|
||||
_showMaps() {
|
||||
this.transitionView.style.display = "none";
|
||||
DOM.removeAllChildren(this.transitionView);
|
||||
this._displayedMapsInTree = new Set();
|
||||
// Limit view to 200 maps for performance reasons.
|
||||
this.selectedMapLogEntries.slice(0, 200).forEach((map) =>
|
||||
this.addMapAndParentTransitions(map));
|
||||
this._displayedMapsInTree = undefined;
|
||||
this.transitionView.style.display = "";
|
||||
}
|
||||
|
||||
set selectedMapLogEntries(list) {
|
||||
this._selectedMapLogEntries = list;
|
||||
this.showMaps();
|
||||
}
|
||||
|
||||
get selectedMapLogEntries() {
|
||||
return this._selectedMapLogEntries;
|
||||
}
|
||||
|
||||
addMapAndParentTransitions(map) {
|
||||
if (map === void 0) return;
|
||||
if (this._displayedMapsInTree.has(map)) return;
|
||||
this._displayedMapsInTree.add(map);
|
||||
this.currentNode = this.transitionView;
|
||||
let parents = map.getParents();
|
||||
if (parents.length > 0) {
|
||||
this.addTransitionTo(parents.pop());
|
||||
parents.reverse().forEach((each) => this.addTransitionTo(each));
|
||||
}
|
||||
let mapNode = this.addSubtransitions(map);
|
||||
// Mark and show the selected map.
|
||||
mapNode.classList.add("selected");
|
||||
if (this.selectedMap == map) {
|
||||
setTimeout(
|
||||
() =>
|
||||
mapNode.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "nearest",
|
||||
}),
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
addSubtransitions(map) {
|
||||
let mapNode = this.addTransitionTo(map);
|
||||
// Draw outgoing linear transition line.
|
||||
let current = map;
|
||||
while (current.children.length == 1) {
|
||||
current = current.children[0].to;
|
||||
this.addTransitionTo(current);
|
||||
}
|
||||
return mapNode;
|
||||
}
|
||||
|
||||
addTransitionEdge(map) {
|
||||
let classes = ["transitionEdge"];
|
||||
let edge = DOM.div(classes);
|
||||
edge.style.backgroundColor = typeToColor(map.edge);
|
||||
let labelNode = DOM.div("transitionLabel");
|
||||
labelNode.innerText = map.edge.toString();
|
||||
edge.appendChild(labelNode);
|
||||
return edge;
|
||||
}
|
||||
|
||||
addTransitionTo(map) {
|
||||
// transition[ transitions[ transition[...], transition[...], ...]];
|
||||
this._displayedMapsInTree?.add(map);
|
||||
let transition = DOM.div("transition");
|
||||
if (map.isDeprecated()) transition.classList.add("deprecated");
|
||||
if (map.edge) {
|
||||
transition.appendChild(this.addTransitionEdge(map));
|
||||
}
|
||||
let mapNode = this.addMapNode(map);
|
||||
transition.appendChild(mapNode);
|
||||
|
||||
let subtree = DOM.div("transitions");
|
||||
transition.appendChild(subtree);
|
||||
|
||||
this.currentNode.appendChild(transition);
|
||||
this.currentNode = subtree;
|
||||
|
||||
return mapNode;
|
||||
}
|
||||
|
||||
addMapNode(map) {
|
||||
let node = DOM.div("map");
|
||||
if (map.edge) node.style.backgroundColor = typeToColor(map.edge);
|
||||
node.map = map;
|
||||
node.addEventListener("click", () => this.selectMap(map));
|
||||
if (map.children.length > 1) {
|
||||
node.innerText = map.children.length;
|
||||
let showSubtree = DOM.div("showSubtransitions");
|
||||
showSubtree.addEventListener("click", (e) =>
|
||||
this.toggleSubtree(e, node)
|
||||
);
|
||||
node.appendChild(showSubtree);
|
||||
} else if (map.children.length == 0) {
|
||||
node.innerHTML = "●";
|
||||
}
|
||||
this.currentNode.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
toggleSubtree(event, node) {
|
||||
let map = node.map;
|
||||
event.target.classList.toggle("opened");
|
||||
let transitionsNode = node.parentElement.querySelector(".transitions");
|
||||
let subtransitionNodes = transitionsNode.children;
|
||||
if (subtransitionNodes.length <= 1) {
|
||||
// Add subtransitions excepth the one that's already shown.
|
||||
let visibleTransitionMap =
|
||||
subtransitionNodes.length == 1
|
||||
? transitionsNode.querySelector(".map").map
|
||||
: void 0;
|
||||
map.children.forEach((edge) => {
|
||||
if (edge.to != visibleTransitionMap) {
|
||||
this.currentNode = transitionsNode;
|
||||
this.addSubtransitions(edge.to);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// remove all but the first (currently selected) subtransition
|
||||
for (let i = subtransitionNodes.length - 1; i > 0; i--) {
|
||||
transitionsNode.removeChild(subtransitionNodes[i]);
|
||||
}
|
||||
handleTransitionViewChange(e) {
|
||||
this.tooltip.style.left = e.pageX + 'px';
|
||||
this.tooltip.style.top = e.pageY + 'px';
|
||||
let map = e.target.map;
|
||||
if (map) {
|
||||
this.tooltipContents.innerText = map.description;
|
||||
}
|
||||
}
|
||||
|
||||
selectMap(map) {
|
||||
this.dispatchEvent(new SelectionEvent([map]));
|
||||
}
|
||||
|
||||
showMap() {
|
||||
if (this.currentMap === this._map) return;
|
||||
this.currentMap = this._map;
|
||||
this.selectedMapLogEntries = [this._map];
|
||||
this.showMaps();
|
||||
}
|
||||
|
||||
showMaps() {
|
||||
clearTimeout(this._showMapsUpdateId);
|
||||
this._showMapsUpdateId = setTimeout(() => this._showMaps(), 250);
|
||||
}
|
||||
_showMaps() {
|
||||
this.transitionView.style.display = 'none';
|
||||
DOM.removeAllChildren(this.transitionView);
|
||||
this._displayedMapsInTree = new Set();
|
||||
// Limit view to 200 maps for performance reasons.
|
||||
this.selectedMapLogEntries.slice(0, 200).forEach(
|
||||
(map) => this.addMapAndParentTransitions(map));
|
||||
this._displayedMapsInTree = undefined;
|
||||
this.transitionView.style.display = '';
|
||||
}
|
||||
|
||||
set selectedMapLogEntries(list) {
|
||||
this._selectedMapLogEntries = list;
|
||||
this.showMaps();
|
||||
}
|
||||
|
||||
get selectedMapLogEntries() {
|
||||
return this._selectedMapLogEntries;
|
||||
}
|
||||
|
||||
addMapAndParentTransitions(map) {
|
||||
if (map === void 0) return;
|
||||
if (this._displayedMapsInTree.has(map)) return;
|
||||
this._displayedMapsInTree.add(map);
|
||||
this.currentNode = this.transitionView;
|
||||
let parents = map.getParents();
|
||||
if (parents.length > 0) {
|
||||
this.addTransitionTo(parents.pop());
|
||||
parents.reverse().forEach((each) => this.addTransitionTo(each));
|
||||
}
|
||||
let mapNode = this.addSubtransitions(map);
|
||||
// Mark and show the selected map.
|
||||
mapNode.classList.add('selected');
|
||||
if (this.selectedMap == map) {
|
||||
setTimeout(
|
||||
() => mapNode.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'nearest',
|
||||
}),
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
addSubtransitions(map) {
|
||||
let mapNode = this.addTransitionTo(map);
|
||||
// Draw outgoing linear transition line.
|
||||
let current = map;
|
||||
while (current.children.length == 1) {
|
||||
current = current.children[0].to;
|
||||
this.addTransitionTo(current);
|
||||
}
|
||||
return mapNode;
|
||||
}
|
||||
|
||||
addTransitionEdge(map) {
|
||||
let classes = ['transitionEdge'];
|
||||
let edge = DOM.div(classes);
|
||||
edge.style.backgroundColor = typeToColor(map.edge);
|
||||
let labelNode = DOM.div('transitionLabel');
|
||||
labelNode.innerText = map.edge.toString();
|
||||
edge.appendChild(labelNode);
|
||||
return edge;
|
||||
}
|
||||
|
||||
addTransitionTo(map) {
|
||||
// transition[ transitions[ transition[...], transition[...], ...]];
|
||||
this._displayedMapsInTree?.add(map);
|
||||
let transition = DOM.div('transition');
|
||||
if (map.isDeprecated()) transition.classList.add('deprecated');
|
||||
if (map.edge) {
|
||||
transition.appendChild(this.addTransitionEdge(map));
|
||||
}
|
||||
let mapNode = this.addMapNode(map);
|
||||
transition.appendChild(mapNode);
|
||||
|
||||
let subtree = DOM.div('transitions');
|
||||
transition.appendChild(subtree);
|
||||
|
||||
this.currentNode.appendChild(transition);
|
||||
this.currentNode = subtree;
|
||||
|
||||
return mapNode;
|
||||
}
|
||||
|
||||
addMapNode(map) {
|
||||
let node = DOM.div('map');
|
||||
if (map.edge) node.style.backgroundColor = typeToColor(map.edge);
|
||||
node.map = map;
|
||||
node.addEventListener('click', () => this.selectMap(map));
|
||||
if (map.children.length > 1) {
|
||||
node.innerText = map.children.length;
|
||||
let showSubtree = DOM.div('showSubtransitions');
|
||||
showSubtree.addEventListener('click', (e) => this.toggleSubtree(e, node));
|
||||
node.appendChild(showSubtree);
|
||||
} else if (map.children.length == 0) {
|
||||
node.innerHTML = '●';
|
||||
}
|
||||
this.currentNode.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
toggleSubtree(event, node) {
|
||||
let map = node.map;
|
||||
event.target.classList.toggle('opened');
|
||||
let transitionsNode = node.parentElement.querySelector('.transitions');
|
||||
let subtransitionNodes = transitionsNode.children;
|
||||
if (subtransitionNodes.length <= 1) {
|
||||
// Add subtransitions excepth the one that's already shown.
|
||||
let visibleTransitionMap = subtransitionNodes.length == 1 ?
|
||||
transitionsNode.querySelector('.map').map :
|
||||
void 0;
|
||||
map.children.forEach((edge) => {
|
||||
if (edge.to != visibleTransitionMap) {
|
||||
this.currentNode = transitionsNode;
|
||||
this.addSubtransitions(edge.to);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// remove all but the first (currently selected) subtransition
|
||||
for (let i = subtransitionNodes.length - 1; i > 0; i--) {
|
||||
transitionsNode.removeChild(subtransitionNodes[i]);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -2,12 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import { MapLogEntry, Edge } from "./log/map.mjs";
|
||||
import { IcLogEntry } from "./log/ic.mjs";
|
||||
import { DeoptLogEntry } from "./log/deopt.mjs";
|
||||
import { Timeline } from "./timeline.mjs";
|
||||
import { LogReader, parseString, parseVarArgs } from "../logreader.mjs";
|
||||
import { Profile } from "../profile.mjs";
|
||||
import {LogReader, parseString, parseVarArgs} from '../logreader.mjs';
|
||||
import {Profile} from '../profile.mjs';
|
||||
|
||||
import {DeoptLogEntry} from './log/deopt.mjs';
|
||||
import {IcLogEntry} from './log/ic.mjs';
|
||||
import {Edge, MapLogEntry} from './log/map.mjs';
|
||||
import {Timeline} from './timeline.mjs';
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
@ -43,7 +44,8 @@ export class Processor extends LogReader {
|
||||
},
|
||||
'v8-version': {
|
||||
parsers: [
|
||||
parseInt, parseInt,
|
||||
parseInt,
|
||||
parseInt,
|
||||
],
|
||||
processor: this.processV8Version
|
||||
},
|
||||
@ -52,12 +54,12 @@ export class Processor extends LogReader {
|
||||
processor: this.processScriptSource
|
||||
},
|
||||
'code-move':
|
||||
{ parsers: [parseInt, parseInt], processor: this.processCodeMove },
|
||||
'code-delete': { parsers: [parseInt], processor: this.processCodeDelete },
|
||||
{parsers: [parseInt, parseInt], processor: this.processCodeMove},
|
||||
'code-delete': {parsers: [parseInt], processor: this.processCodeDelete},
|
||||
'sfi-move':
|
||||
{ parsers: [parseInt, parseInt], processor: this.processFunctionMove },
|
||||
{parsers: [parseInt, parseInt], processor: this.processFunctionMove},
|
||||
'map-create':
|
||||
{ parsers: [parseInt, parseString], processor: this.processMapCreate },
|
||||
{parsers: [parseInt, parseString], processor: this.processMapCreate},
|
||||
'map': {
|
||||
parsers: [
|
||||
parseString, parseInt, parseString, parseString, parseInt, parseInt,
|
||||
@ -140,8 +142,8 @@ export class Processor extends LogReader {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
'Error occurred during parsing line ' + i +
|
||||
', trying to continue: ' + e);
|
||||
'Error occurred during parsing line ' + i +
|
||||
', trying to continue: ' + e);
|
||||
}
|
||||
this.finalize();
|
||||
}
|
||||
@ -184,24 +186,25 @@ export class Processor extends LogReader {
|
||||
let funcAddr = parseInt(maybe_func[0]);
|
||||
let state = this.parseState(maybe_func[1]);
|
||||
this._profile.addFuncCode(
|
||||
type, name, timestamp, start, size, funcAddr, state);
|
||||
type, name, timestamp, start, size, funcAddr, state);
|
||||
} else {
|
||||
this._profile.addCode(type, name, timestamp, start, size);
|
||||
}
|
||||
}
|
||||
|
||||
processCodeDeopt(timestamp, codeSize, instructionStart, inliningId,
|
||||
scriptOffset, deoptKind, deoptLocation, deoptReason) {
|
||||
processCodeDeopt(
|
||||
timestamp, codeSize, instructionStart, inliningId, scriptOffset,
|
||||
deoptKind, deoptLocation, deoptReason) {
|
||||
this._deoptTimeline.push(new DeoptLogEntry(deoptKind, timestamp));
|
||||
}
|
||||
|
||||
processV8Version(majorVersion, minorVersion) {
|
||||
if (
|
||||
(majorVersion == this.MAJOR_VERSION && minorVersion <= this.MINOR_VERSION)
|
||||
|| (majorVersion < this.MAJOR_VERSION)) {
|
||||
if ((majorVersion == this.MAJOR_VERSION &&
|
||||
minorVersion <= this.MINOR_VERSION) ||
|
||||
(majorVersion < this.MAJOR_VERSION)) {
|
||||
window.alert(
|
||||
`Unsupported version ${majorVersion}.${minorVersion}. \n` +
|
||||
`Please use the matching tool for given the V8 version.`);
|
||||
`Unsupported version ${majorVersion}.${minorVersion}. \n` +
|
||||
`Please use the matching tool for given the V8 version.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,16 +234,16 @@ export class Processor extends LogReader {
|
||||
}
|
||||
|
||||
processPropertyIC(
|
||||
type, pc, time, line, column, old_state, new_state, map, key, modifier,
|
||||
slow_reason) {
|
||||
type, pc, time, line, column, old_state, new_state, map, key, modifier,
|
||||
slow_reason) {
|
||||
let fnName = this.functionName(pc);
|
||||
let parts = fnName.split(' ');
|
||||
let fileName = parts[parts.length-1];
|
||||
let fileName = parts[parts.length - 1];
|
||||
let script = this.getScript(fileName);
|
||||
// TODO: Use SourcePosition here directly
|
||||
let entry = new IcLogEntry(
|
||||
type, fnName, time, line, column, key, old_state, new_state, map,
|
||||
slow_reason, script, modifier);
|
||||
type, fnName, time, line, column, key, old_state, new_state, map,
|
||||
slow_reason, script, modifier);
|
||||
if (script) {
|
||||
entry.sourcePosition = script.addSourcePosition(line, column, entry);
|
||||
}
|
||||
@ -254,9 +257,9 @@ export class Processor extends LogReader {
|
||||
formatPC(pc, line, column) {
|
||||
let entry = this._profile.findEntry(pc);
|
||||
if (!entry) return '<unknown>'
|
||||
if (entry.type === 'Builtin') {
|
||||
return entry.name;
|
||||
}
|
||||
if (entry.type === 'Builtin') {
|
||||
return entry.name;
|
||||
}
|
||||
let name = entry.func.getName();
|
||||
let array = this._formatPCRegexp.exec(name);
|
||||
if (array === null) {
|
||||
@ -272,7 +275,7 @@ export class Processor extends LogReader {
|
||||
// Try to handle urls with file positions: https://foo.bar.com/:17:330"
|
||||
filePositionLine = filePositionLine.split(' ');
|
||||
let parts = filePositionLine[1].split(':');
|
||||
if (parts[0].length <= 5) return parts[0] + ':' + parts[1]
|
||||
if (parts[0].length <= 5) return parts[0] + ':' + parts[1];
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
|
@ -1,138 +1,133 @@
|
||||
// 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 { V8CustomElement, DOM, delay, formatBytes} from "./helper.mjs";
|
||||
import { SelectionEvent, FocusEvent } from "./events.mjs";
|
||||
import { MapLogEntry } from "./log/map.mjs";
|
||||
import { IcLogEntry } from "./log/ic.mjs";
|
||||
import {FocusEvent, SelectionEvent} from './events.mjs';
|
||||
import {delay, DOM, formatBytes, V8CustomElement} from './helper.mjs';
|
||||
import {IcLogEntry} from './log/ic.mjs';
|
||||
import {MapLogEntry} from './log/map.mjs';
|
||||
|
||||
DOM.defineCustomElement(
|
||||
"source-panel",
|
||||
(templateText) =>
|
||||
class SourcePanel extends V8CustomElement {
|
||||
_selectedSourcePositions = [];
|
||||
_sourcePositionsToMarkNodes;
|
||||
_scripts = [];
|
||||
_script;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.scriptDropdown.addEventListener(
|
||||
'change', e => this._handleSelectScript(e));
|
||||
}
|
||||
DOM.defineCustomElement('source-panel',
|
||||
(templateText) =>
|
||||
class SourcePanel extends V8CustomElement {
|
||||
_selectedSourcePositions = [];
|
||||
_sourcePositionsToMarkNodes;
|
||||
_scripts = [];
|
||||
_script;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.scriptDropdown.addEventListener(
|
||||
'change', e => this._handleSelectScript(e));
|
||||
}
|
||||
|
||||
get script() {
|
||||
return this.$('#script');
|
||||
}
|
||||
get script() {
|
||||
return this.$('#script');
|
||||
}
|
||||
|
||||
get scriptNode() {
|
||||
return this.$('.scriptNode');
|
||||
}
|
||||
get scriptNode() {
|
||||
return this.$('.scriptNode');
|
||||
}
|
||||
|
||||
set script(script) {
|
||||
if (this._script === script) return;
|
||||
this._script = script;
|
||||
this._renderSourcePanel();
|
||||
this._updateScriptDropdownSelection();
|
||||
}
|
||||
set script(script) {
|
||||
if (this._script === script) return;
|
||||
this._script = script;
|
||||
this._renderSourcePanel();
|
||||
this._updateScriptDropdownSelection();
|
||||
}
|
||||
|
||||
set selectedSourcePositions(sourcePositions) {
|
||||
this._selectedSourcePositions = sourcePositions;
|
||||
// TODO: highlight multiple scripts
|
||||
this.script = sourcePositions[0]?.script;
|
||||
this._focusSelectedMarkers();
|
||||
}
|
||||
set selectedSourcePositions(sourcePositions) {
|
||||
this._selectedSourcePositions = sourcePositions;
|
||||
// TODO: highlight multiple scripts
|
||||
this.script = sourcePositions[0]?.script;
|
||||
this._focusSelectedMarkers();
|
||||
}
|
||||
|
||||
set data(scripts) {
|
||||
this._scripts = scripts;
|
||||
this._initializeScriptDropdown();
|
||||
}
|
||||
set data(scripts) {
|
||||
this._scripts = scripts;
|
||||
this._initializeScriptDropdown();
|
||||
}
|
||||
|
||||
get scriptDropdown() {
|
||||
return this.$("#script-dropdown");
|
||||
}
|
||||
get scriptDropdown() {
|
||||
return this.$('#script-dropdown');
|
||||
}
|
||||
|
||||
_initializeScriptDropdown() {
|
||||
this._scripts.sort((a, b) => a.name.localeCompare(b.name));
|
||||
let select = this.scriptDropdown;
|
||||
select.options.length = 0;
|
||||
for (const script of this._scripts) {
|
||||
const option = document.createElement("option");
|
||||
const size = formatBytes(script.source.length);
|
||||
option.text = `${script.name} (id=${script.id} size=${size})`;
|
||||
option.script = script;
|
||||
select.add(option);
|
||||
}
|
||||
}
|
||||
_updateScriptDropdownSelection() {
|
||||
this.scriptDropdown.selectedIndex =
|
||||
this._script ? this._scripts.indexOf(this._script) : -1;
|
||||
}
|
||||
_initializeScriptDropdown() {
|
||||
this._scripts.sort((a, b) => a.name.localeCompare(b.name));
|
||||
let select = this.scriptDropdown;
|
||||
select.options.length = 0;
|
||||
for (const script of this._scripts) {
|
||||
const option = document.createElement('option');
|
||||
const size = formatBytes(script.source.length);
|
||||
option.text = `${script.name} (id=${script.id} size=${size})`;
|
||||
option.script = script;
|
||||
select.add(option);
|
||||
}
|
||||
}
|
||||
_updateScriptDropdownSelection() {
|
||||
this.scriptDropdown.selectedIndex =
|
||||
this._script ? this._scripts.indexOf(this._script) : -1;
|
||||
}
|
||||
|
||||
async _renderSourcePanel() {
|
||||
let scriptNode;
|
||||
if (this._script) {
|
||||
await delay(1);
|
||||
const builder = new LineBuilder(
|
||||
this, this._script, this._selectedSourcePositions);
|
||||
scriptNode = builder.createScriptNode();
|
||||
this._sourcePositionsToMarkNodes = builder.sourcePositionToMarkers;
|
||||
} else {
|
||||
scriptNode = document.createElement("pre");
|
||||
this._selectedMarkNodes = undefined;
|
||||
}
|
||||
const oldScriptNode = this.script.childNodes[1];
|
||||
this.script.replaceChild(scriptNode, oldScriptNode);
|
||||
}
|
||||
async _renderSourcePanel() {
|
||||
let scriptNode;
|
||||
if (this._script) {
|
||||
await delay(1);
|
||||
const builder =
|
||||
new LineBuilder(this, this._script, this._selectedSourcePositions);
|
||||
scriptNode = builder.createScriptNode();
|
||||
this._sourcePositionsToMarkNodes = builder.sourcePositionToMarkers;
|
||||
} else {
|
||||
scriptNode = document.createElement('pre');
|
||||
this._selectedMarkNodes = undefined;
|
||||
}
|
||||
const oldScriptNode = this.script.childNodes[1];
|
||||
this.script.replaceChild(scriptNode, oldScriptNode);
|
||||
}
|
||||
|
||||
async _focusSelectedMarkers() {
|
||||
await delay(100);
|
||||
// Remove all marked nodes.
|
||||
for (let markNode of this._sourcePositionsToMarkNodes.values()) {
|
||||
markNode.className = "";
|
||||
}
|
||||
for (let sourcePosition of this._selectedSourcePositions) {
|
||||
this._sourcePositionsToMarkNodes
|
||||
.get(sourcePosition).className = "marked";
|
||||
}
|
||||
const sourcePosition = this._selectedSourcePositions[0];
|
||||
if (!sourcePosition) return;
|
||||
const markNode = this._sourcePositionsToMarkNodes.get(sourcePosition);
|
||||
markNode.scrollIntoView({
|
||||
behavior: "smooth", block: "nearest", inline: "center"
|
||||
});
|
||||
}
|
||||
async _focusSelectedMarkers() {
|
||||
await delay(100);
|
||||
// Remove all marked nodes.
|
||||
for (let markNode of this._sourcePositionsToMarkNodes.values()) {
|
||||
markNode.className = '';
|
||||
}
|
||||
for (let sourcePosition of this._selectedSourcePositions) {
|
||||
this._sourcePositionsToMarkNodes.get(sourcePosition).className = 'marked';
|
||||
}
|
||||
const sourcePosition = this._selectedSourcePositions[0];
|
||||
if (!sourcePosition) return;
|
||||
const markNode = this._sourcePositionsToMarkNodes.get(sourcePosition);
|
||||
markNode.scrollIntoView(
|
||||
{behavior: 'smooth', block: 'nearest', inline: 'center'});
|
||||
}
|
||||
|
||||
_handleSelectScript(e) {
|
||||
const option =
|
||||
this.scriptDropdown.options[this.scriptDropdown.selectedIndex];
|
||||
this.script = option.script;
|
||||
this.selectLogEntries(this._script.entries());
|
||||
}
|
||||
_handleSelectScript(e) {
|
||||
const option =
|
||||
this.scriptDropdown.options[this.scriptDropdown.selectedIndex];
|
||||
this.script = option.script;
|
||||
this.selectLogEntries(this._script.entries());
|
||||
}
|
||||
|
||||
handleSourcePositionClick(e) {
|
||||
this.selectLogEntries(e.target.sourcePosition.entries)
|
||||
}
|
||||
handleSourcePositionClick(e) {
|
||||
this.selectLogEntries(e.target.sourcePosition.entries)
|
||||
}
|
||||
|
||||
selectLogEntries(logEntries) {
|
||||
let icLogEntries = [];
|
||||
let mapLogEntries = [];
|
||||
for (const entry of logEntries) {
|
||||
if (entry instanceof MapLogEntry) {
|
||||
mapLogEntries.push(entry);
|
||||
} else if (entry instanceof IcLogEntry) {
|
||||
icLogEntries.push(entry);
|
||||
}
|
||||
}
|
||||
if (icLogEntries.length > 0 ) {
|
||||
this.dispatchEvent(new SelectionEvent(icLogEntries));
|
||||
}
|
||||
if (mapLogEntries.length > 0) {
|
||||
this.dispatchEvent(new SelectionEvent(mapLogEntries));
|
||||
}
|
||||
selectLogEntries(logEntries) {
|
||||
let icLogEntries = [];
|
||||
let mapLogEntries = [];
|
||||
for (const entry of logEntries) {
|
||||
if (entry instanceof MapLogEntry) {
|
||||
mapLogEntries.push(entry);
|
||||
} else if (entry instanceof IcLogEntry) {
|
||||
icLogEntries.push(entry);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (icLogEntries.length > 0) {
|
||||
this.dispatchEvent(new SelectionEvent(icLogEntries));
|
||||
}
|
||||
if (mapLogEntries.length > 0) {
|
||||
this.dispatchEvent(new SelectionEvent(mapLogEntries));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class SourcePositionIterator {
|
||||
_entries;
|
||||
@ -141,16 +136,16 @@ class SourcePositionIterator {
|
||||
this._entries = sourcePositions;
|
||||
}
|
||||
|
||||
*forLine(lineIndex) {
|
||||
* forLine(lineIndex) {
|
||||
this._findStart(lineIndex);
|
||||
while(!this._done() && this._current().line === lineIndex) {
|
||||
while (!this._done() && this._current().line === lineIndex) {
|
||||
yield this._current();
|
||||
this._next();
|
||||
}
|
||||
}
|
||||
|
||||
_findStart(lineIndex) {
|
||||
while(!this._done() && this._current().line < lineIndex) {
|
||||
while (!this._done() && this._current().line < lineIndex) {
|
||||
this._next();
|
||||
}
|
||||
}
|
||||
@ -168,11 +163,11 @@ class SourcePositionIterator {
|
||||
}
|
||||
}
|
||||
|
||||
function * lineIterator(source) {
|
||||
function* lineIterator(source) {
|
||||
let current = 0;
|
||||
let line = 1;
|
||||
while(current < source.length) {
|
||||
const next = source.indexOf("\n", current);
|
||||
while (current < source.length) {
|
||||
const next = source.indexOf('\n', current);
|
||||
if (next === -1) break;
|
||||
yield [line, source.substring(current, next)];
|
||||
line++;
|
||||
@ -181,7 +176,6 @@ function * lineIterator(source) {
|
||||
if (current < source.length) yield [line, source.substring(current)];
|
||||
}
|
||||
|
||||
|
||||
class LineBuilder {
|
||||
_script;
|
||||
_clickHandler;
|
||||
@ -194,12 +188,12 @@ class LineBuilder {
|
||||
this._selection = new Set(highlightPositions);
|
||||
this._clickHandler = panel.handleSourcePositionClick.bind(panel);
|
||||
// TODO: sort on script finalization.
|
||||
script.sourcePositions.sort((a, b) => {
|
||||
if (a.line === b.line) return a.column - b.column;
|
||||
return a.line - b.line;
|
||||
})
|
||||
this._sourcePositions
|
||||
= new SourcePositionIterator(script.sourcePositions);
|
||||
script.sourcePositions
|
||||
.sort((a, b) => {
|
||||
if (a.line === b.line) return a.column - b.column;
|
||||
return a.line - b.line;
|
||||
}) this._sourcePositions =
|
||||
new SourcePositionIterator(script.sourcePositions);
|
||||
}
|
||||
|
||||
get sourcePositionToMarkers() {
|
||||
@ -207,7 +201,7 @@ class LineBuilder {
|
||||
}
|
||||
|
||||
createScriptNode() {
|
||||
const scriptNode = document.createElement("pre");
|
||||
const scriptNode = document.createElement('pre');
|
||||
scriptNode.classList.add('scriptNode');
|
||||
for (let [lineIndex, line] of lineIterator(this._script.source)) {
|
||||
scriptNode.appendChild(this._createLineNode(lineIndex, line));
|
||||
@ -216,13 +210,12 @@ class LineBuilder {
|
||||
}
|
||||
|
||||
_createLineNode(lineIndex, line) {
|
||||
const lineNode = document.createElement("span");
|
||||
let columnIndex = 0;
|
||||
const lineNode = document.createElement('span');
|
||||
let columnIndex = 0;
|
||||
for (const sourcePosition of this._sourcePositions.forLine(lineIndex)) {
|
||||
const nextColumnIndex = sourcePosition.column - 1;
|
||||
lineNode.appendChild(
|
||||
document.createTextNode(
|
||||
line.substring(columnIndex, nextColumnIndex)));
|
||||
lineNode.appendChild(document.createTextNode(
|
||||
line.substring(columnIndex, nextColumnIndex)));
|
||||
columnIndex = nextColumnIndex;
|
||||
|
||||
lineNode.appendChild(
|
||||
@ -230,12 +223,12 @@ class LineBuilder {
|
||||
columnIndex++;
|
||||
}
|
||||
lineNode.appendChild(
|
||||
document.createTextNode(line.substring(columnIndex) + "\n"));
|
||||
document.createTextNode(line.substring(columnIndex) + '\n'));
|
||||
return lineNode;
|
||||
}
|
||||
|
||||
_createMarkerNode(text, sourcePosition) {
|
||||
const marker = document.createElement("mark");
|
||||
const marker = document.createElement('mark');
|
||||
this._sourcePositionToMarkers.set(sourcePosition, marker);
|
||||
marker.textContent = text;
|
||||
marker.sourcePosition = sourcePosition;
|
||||
|
@ -1,14 +1,12 @@
|
||||
// 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 { V8CustomElement, DOM} from "./helper.mjs";
|
||||
import { SelectionEvent } from "./events.mjs";
|
||||
import { delay, LazyTable } from "./helper.mjs";
|
||||
import {SelectionEvent} from './events.mjs';
|
||||
import {DOM, V8CustomElement} from './helper.mjs';
|
||||
import {delay, LazyTable} from './helper.mjs';
|
||||
|
||||
DOM.defineCustomElement(
|
||||
"stats-panel",
|
||||
(templateText) =>
|
||||
class StatsPanel extends V8CustomElement {
|
||||
'stats-panel', (templateText) => class StatsPanel extends V8CustomElement {
|
||||
_timeline;
|
||||
_transitions;
|
||||
_selectedLogEntries;
|
||||
@ -17,7 +15,7 @@ DOM.defineCustomElement(
|
||||
}
|
||||
|
||||
get stats() {
|
||||
return this.$("#stats");
|
||||
return this.$('#stats');
|
||||
}
|
||||
|
||||
set timeline(timeline) {
|
||||
@ -51,40 +49,40 @@ DOM.defineCustomElement(
|
||||
}
|
||||
|
||||
updateGeneralStats() {
|
||||
console.assert(this._timeline !== undefined, "Timeline not set yet!");
|
||||
console.assert(this._timeline !== undefined, 'Timeline not set yet!');
|
||||
let pairs = [
|
||||
["Transitions", "primary", (e) => e.edge && e.edge.isTransition()],
|
||||
["Fast to Slow", "violet", (e) => e.edge && e.edge.isFastToSlow()],
|
||||
["Slow to Fast", "orange", (e) => e.edge && e.edge.isSlowToFast()],
|
||||
["Initial Map", "yellow", (e) => e.edge && e.edge.isInitial()],
|
||||
['Transitions', 'primary', (e) => e.edge && e.edge.isTransition()],
|
||||
['Fast to Slow', 'violet', (e) => e.edge && e.edge.isFastToSlow()],
|
||||
['Slow to Fast', 'orange', (e) => e.edge && e.edge.isSlowToFast()],
|
||||
['Initial Map', 'yellow', (e) => e.edge && e.edge.isInitial()],
|
||||
[
|
||||
"Replace Descriptors",
|
||||
"red",
|
||||
'Replace Descriptors',
|
||||
'red',
|
||||
(e) => e.edge && e.edge.isReplaceDescriptors(),
|
||||
],
|
||||
[
|
||||
"Copy as Prototype",
|
||||
"red",
|
||||
'Copy as Prototype',
|
||||
'red',
|
||||
(e) => e.edge && e.edge.isCopyAsPrototype(),
|
||||
],
|
||||
[
|
||||
"Optimize as Prototype",
|
||||
'Optimize as Prototype',
|
||||
null,
|
||||
(e) => e.edge && e.edge.isOptimizeAsPrototype(),
|
||||
],
|
||||
["Deprecated", null, (e) => e.isDeprecated()],
|
||||
["Bootstrapped", "green", (e) => e.isBootstrapped()],
|
||||
["Total", null, (e) => true],
|
||||
['Deprecated', null, (e) => e.isDeprecated()],
|
||||
['Bootstrapped', 'green', (e) => e.isBootstrapped()],
|
||||
['Total', null, (e) => true],
|
||||
];
|
||||
|
||||
let tbody = document.createElement("tbody");
|
||||
let tbody = document.createElement('tbody');
|
||||
let total = this._selectedLogEntries.length;
|
||||
pairs.forEach(([name, color, filter]) => {
|
||||
let row = DOM.tr();
|
||||
if (color !== null) {
|
||||
row.appendChild(DOM.td(DOM.div(["colorbox", color])));
|
||||
row.appendChild(DOM.td(DOM.div(['colorbox', color])));
|
||||
} else {
|
||||
row.appendChild(DOM.td(""));
|
||||
row.appendChild(DOM.td(''));
|
||||
}
|
||||
row.classList.add('clickable');
|
||||
row.onclick = (e) => {
|
||||
@ -99,10 +97,10 @@ DOM.defineCustomElement(
|
||||
let count = this.count(filter);
|
||||
row.appendChild(DOM.td(count));
|
||||
let percent = Math.round((count / total) * 1000) / 10;
|
||||
row.appendChild(DOM.td(percent.toFixed(1) + "%"));
|
||||
row.appendChild(DOM.td(percent.toFixed(1) + '%'));
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
this.$("#typeTable").replaceChild(tbody, this.$("#typeTable tbody"));
|
||||
this.$('#typeTable').replaceChild(tbody, this.$('#typeTable tbody'));
|
||||
}
|
||||
|
||||
count(filter) {
|
||||
@ -116,21 +114,17 @@ DOM.defineCustomElement(
|
||||
updateNamedTransitionsStats() {
|
||||
let rowData = Array.from(this._transitions.entries());
|
||||
rowData.sort((a, b) => b[1].length - a[1].length);
|
||||
new LazyTable(this.$("#nameTable"), rowData, ([name, maps]) => {
|
||||
let row = DOM.tr();
|
||||
row.maps = maps;
|
||||
row.classList.add('clickable');
|
||||
row.addEventListener("click", (e) =>
|
||||
this.dispatchEvent(
|
||||
new SelectionEvent(
|
||||
e.target.parentNode.maps.map((map) => map.to)
|
||||
)
|
||||
)
|
||||
);
|
||||
row.appendChild(DOM.td(maps.length));
|
||||
row.appendChild(DOM.td(name));
|
||||
return row;
|
||||
});
|
||||
new LazyTable(this.$('#nameTable'), rowData, ([name, maps]) => {
|
||||
let row = DOM.tr();
|
||||
row.maps = maps;
|
||||
row.classList.add('clickable');
|
||||
row.addEventListener(
|
||||
'click',
|
||||
(e) => this.dispatchEvent(new SelectionEvent(
|
||||
e.target.parentNode.maps.map((map) => map.to))));
|
||||
row.appendChild(DOM.td(maps.length));
|
||||
row.appendChild(DOM.td(name));
|
||||
return row;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -2,53 +2,55 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import { DOM, V8CustomElement } from './helper.mjs';
|
||||
import { SynchronizeSelectionEvent } from './events.mjs';
|
||||
import './timeline/timeline-track.mjs';
|
||||
|
||||
DOM.defineCustomElement('timeline-panel', (templateText) =>
|
||||
class TimelinePanel extends V8CustomElement {
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.addEventListener(
|
||||
'scrolltrack', e => this.handleTrackScroll(e));
|
||||
this.addEventListener(
|
||||
SynchronizeSelectionEvent.name,
|
||||
e => this.handleSelectionSyncronization(e));
|
||||
}
|
||||
import {SynchronizeSelectionEvent} from './events.mjs';
|
||||
import {DOM, V8CustomElement} from './helper.mjs';
|
||||
|
||||
set nofChunks(count) {
|
||||
for (const track of this.timelineTracks) {
|
||||
track.nofChunks = count;
|
||||
DOM.defineCustomElement(
|
||||
'timeline-panel',
|
||||
(templateText) => class TimelinePanel extends V8CustomElement {
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.addEventListener('scrolltrack', e => this.handleTrackScroll(e));
|
||||
this.addEventListener(
|
||||
SynchronizeSelectionEvent.name,
|
||||
e => this.handleSelectionSyncronization(e));
|
||||
}
|
||||
}
|
||||
|
||||
get nofChunks() {
|
||||
return this.timelineTracks[0].nofChunks;
|
||||
}
|
||||
|
||||
get timelineTracks() {
|
||||
return this.$("slot").assignedNodes().filter(
|
||||
node => node.nodeType === Node.ELEMENT_NODE);
|
||||
}
|
||||
|
||||
handleTrackScroll(event) {
|
||||
//TODO(zcankara) add forEachTrack helper method
|
||||
for (const track of this.timelineTracks) {
|
||||
track.scrollLeft = event.detail;
|
||||
set nofChunks(count) {
|
||||
for (const track of this.timelineTracks) {
|
||||
track.nofChunks = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleSelectionSyncronization(event) {
|
||||
this.timeSelection = {start:event.start, end:event.end};
|
||||
}
|
||||
get nofChunks() {
|
||||
return this.timelineTracks[0].nofChunks;
|
||||
}
|
||||
|
||||
set timeSelection(timeSelection) {
|
||||
if (timeSelection.start > timeSelection.end) {
|
||||
throw new Error("Invalid time range");
|
||||
get timelineTracks() {
|
||||
return this.$('slot').assignedNodes().filter(
|
||||
node => node.nodeType === Node.ELEMENT_NODE);
|
||||
}
|
||||
for (const track of this.timelineTracks) {
|
||||
track.timeSelection = timeSelection;;
|
||||
|
||||
handleTrackScroll(event) {
|
||||
// TODO(zcankara) add forEachTrack helper method
|
||||
for (const track of this.timelineTracks) {
|
||||
track.scrollLeft = event.detail;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
handleSelectionSyncronization(event) {
|
||||
this.timeSelection = {start: event.start, end: event.end};
|
||||
}
|
||||
|
||||
set timeSelection(timeSelection) {
|
||||
if (timeSelection.start > timeSelection.end) {
|
||||
throw new Error('Invalid time range');
|
||||
}
|
||||
for (const track of this.timelineTracks) {
|
||||
track.timeSelection = timeSelection;
|
||||
;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -35,17 +35,16 @@ class Timeline {
|
||||
}
|
||||
|
||||
selectTimeRange(start, end) {
|
||||
this._selection = this.filter(
|
||||
e => e.time >= start && e.time <= end);
|
||||
this._selection = this.filter(e => e.time >= start && e.time <= end);
|
||||
}
|
||||
|
||||
getChunks(windowSizeMs) {
|
||||
//TODO(zcankara) Fill this one
|
||||
// TODO(zcankara) Fill this one
|
||||
return this.chunkSizes(windowSizeMs);
|
||||
}
|
||||
|
||||
get values() {
|
||||
//TODO(zcankara) Not to break something delete later
|
||||
// TODO(zcankara) Not to break something delete later
|
||||
return this._values;
|
||||
}
|
||||
|
||||
@ -253,7 +252,7 @@ class Chunk {
|
||||
if (event_fn === void 0) {
|
||||
event_fn = each => each;
|
||||
}
|
||||
let breakdown = { __proto__: null };
|
||||
let breakdown = {__proto__: null};
|
||||
this.items.forEach(each => {
|
||||
const type = event_fn(each);
|
||||
const v = breakdown[type];
|
||||
@ -265,7 +264,6 @@ class Chunk {
|
||||
filter() {
|
||||
return this.items.filter(map => !map.parent() || !this.has(map.parent()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Timeline, Chunk };
|
||||
export {Timeline, Chunk};
|
||||
|
@ -2,12 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import { DOM, V8CustomElement, CSSColor, delay } from '../helper.mjs';
|
||||
import { kChunkWidth, kChunkHeight } from "../log/map.mjs";
|
||||
import {
|
||||
SelectionEvent, FocusEvent, SelectTimeEvent,
|
||||
SynchronizeSelectionEvent
|
||||
} from '../events.mjs';
|
||||
import {FocusEvent, SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent} from '../events.mjs';
|
||||
import {CSSColor, delay, DOM, V8CustomElement} from '../helper.mjs';
|
||||
import {kChunkHeight, kChunkWidth} from '../log/map.mjs';
|
||||
|
||||
const kColors = [
|
||||
CSSColor.green,
|
||||
@ -21,500 +18,499 @@ const kColors = [
|
||||
CSSColor.secondaryColor,
|
||||
];
|
||||
|
||||
DOM.defineCustomElement('./timeline/timeline-track', (templateText) =>
|
||||
class TimelineTrack extends V8CustomElement {
|
||||
// TODO turn into static field once Safari supports it.
|
||||
static get SELECTION_OFFSET() { return 10 };
|
||||
_timeline;
|
||||
_nofChunks = 400;
|
||||
_chunks;
|
||||
_selectedEntry;
|
||||
_timeToPixel;
|
||||
_timeSelection = { start: -1, end: Infinity };
|
||||
_timeStartOffset;
|
||||
_selectionOriginTime;
|
||||
_typeToColor;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.timeline.addEventListener("scroll",
|
||||
e => this.handleTimelineScroll(e));
|
||||
this.timeline.addEventListener("mousedown",
|
||||
e => this.handleTimeSelectionMouseDown(e));
|
||||
this.timeline.addEventListener("mouseup",
|
||||
e => this.handleTimeSelectionMouseUp(e));
|
||||
this.timeline.addEventListener("mousemove",
|
||||
e => this.handleTimeSelectionMouseMove(e));
|
||||
this.backgroundCanvas = document.createElement('canvas');
|
||||
this.isLocked = false;
|
||||
}
|
||||
DOM.defineCustomElement('./timeline/timeline-track',
|
||||
(templateText) =>
|
||||
class TimelineTrack extends V8CustomElement {
|
||||
// TODO turn into static field once Safari supports it.
|
||||
static get SELECTION_OFFSET() {
|
||||
return 10
|
||||
};
|
||||
_timeline;
|
||||
_nofChunks = 400;
|
||||
_chunks;
|
||||
_selectedEntry;
|
||||
_timeToPixel;
|
||||
_timeSelection = {start: -1, end: Infinity};
|
||||
_timeStartOffset;
|
||||
_selectionOriginTime;
|
||||
_typeToColor;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.timeline.addEventListener('scroll', e => this.handleTimelineScroll(e));
|
||||
this.timeline.addEventListener(
|
||||
'mousedown', e => this.handleTimeSelectionMouseDown(e));
|
||||
this.timeline.addEventListener(
|
||||
'mouseup', e => this.handleTimeSelectionMouseUp(e));
|
||||
this.timeline.addEventListener(
|
||||
'mousemove', e => this.handleTimeSelectionMouseMove(e));
|
||||
this.backgroundCanvas = document.createElement('canvas');
|
||||
this.isLocked = false;
|
||||
}
|
||||
|
||||
handleTimeSelectionMouseDown(e) {
|
||||
let xPosition = e.clientX
|
||||
// Update origin time in case we click on a handle.
|
||||
if (this.isOnLeftHandle(xPosition)) {
|
||||
xPosition = this.rightHandlePosX;
|
||||
} else if (this.isOnRightHandle(xPosition)) {
|
||||
xPosition = this.leftHandlePosX;
|
||||
}
|
||||
this._selectionOriginTime = this.positionToTime(xPosition);
|
||||
handleTimeSelectionMouseDown(e) {
|
||||
let xPosition = e.clientX
|
||||
// Update origin time in case we click on a handle.
|
||||
if (this.isOnLeftHandle(xPosition)) {
|
||||
xPosition = this.rightHandlePosX;
|
||||
}
|
||||
|
||||
isOnLeftHandle(posX) {
|
||||
return (Math.abs(this.leftHandlePosX - posX)
|
||||
<= TimelineTrack.SELECTION_OFFSET);
|
||||
else if (this.isOnRightHandle(xPosition)) {
|
||||
xPosition = this.leftHandlePosX;
|
||||
}
|
||||
this._selectionOriginTime = this.positionToTime(xPosition);
|
||||
}
|
||||
|
||||
isOnRightHandle(posX) {
|
||||
return (Math.abs(this.rightHandlePosX - posX)
|
||||
<= TimelineTrack.SELECTION_OFFSET);
|
||||
}
|
||||
isOnLeftHandle(posX) {
|
||||
return (
|
||||
Math.abs(this.leftHandlePosX - posX) <= TimelineTrack.SELECTION_OFFSET);
|
||||
}
|
||||
|
||||
handleTimeSelectionMouseMove(e) {
|
||||
if (!this._isSelecting) return;
|
||||
const currentTime = this.positionToTime(e.clientX);
|
||||
this.dispatchEvent(new SynchronizeSelectionEvent(
|
||||
isOnRightHandle(posX) {
|
||||
return (
|
||||
Math.abs(this.rightHandlePosX - posX) <=
|
||||
TimelineTrack.SELECTION_OFFSET);
|
||||
}
|
||||
|
||||
handleTimeSelectionMouseMove(e) {
|
||||
if (!this._isSelecting) return;
|
||||
const currentTime = this.positionToTime(e.clientX);
|
||||
this.dispatchEvent(new SynchronizeSelectionEvent(
|
||||
Math.min(this._selectionOriginTime, currentTime),
|
||||
Math.max(this._selectionOriginTime, currentTime)));
|
||||
}
|
||||
}
|
||||
|
||||
handleTimeSelectionMouseUp(e) {
|
||||
this._selectionOriginTime = -1;
|
||||
const delta = this._timeSelection.end - this._timeSelection.start;
|
||||
if (delta <= 1 || isNaN(delta)) return;
|
||||
this.dispatchEvent(new SelectTimeEvent(this._timeSelection.start,
|
||||
this._timeSelection.end));
|
||||
}
|
||||
handleTimeSelectionMouseUp(e) {
|
||||
this._selectionOriginTime = -1;
|
||||
const delta = this._timeSelection.end - this._timeSelection.start;
|
||||
if (delta <= 1 || isNaN(delta)) return;
|
||||
this.dispatchEvent(new SelectTimeEvent(
|
||||
this._timeSelection.start, this._timeSelection.end));
|
||||
}
|
||||
|
||||
set timeSelection(selection) {
|
||||
this._timeSelection.start = selection.start;
|
||||
this._timeSelection.end= selection.end;
|
||||
this.updateSelection();
|
||||
}
|
||||
set timeSelection(selection) {
|
||||
this._timeSelection.start = selection.start;
|
||||
this._timeSelection.end = selection.end;
|
||||
this.updateSelection();
|
||||
}
|
||||
|
||||
get _isSelecting() {
|
||||
return this._selectionOriginTime >= 0;
|
||||
}
|
||||
get _isSelecting() {
|
||||
return this._selectionOriginTime >= 0;
|
||||
}
|
||||
|
||||
updateSelection() {
|
||||
const startPosition = this.timeToPosition(this._timeSelection.start);
|
||||
const endPosition = this.timeToPosition(this._timeSelection.end);
|
||||
const delta = endPosition - startPosition;
|
||||
this.leftHandle.style.left = startPosition + "px";
|
||||
this.selection.style.left = startPosition + "px";
|
||||
this.rightHandle.style.left = endPosition + "px";
|
||||
this.selection.style.width = delta + "px";
|
||||
}
|
||||
updateSelection() {
|
||||
const startPosition = this.timeToPosition(this._timeSelection.start);
|
||||
const endPosition = this.timeToPosition(this._timeSelection.end);
|
||||
const delta = endPosition - startPosition;
|
||||
this.leftHandle.style.left = startPosition + 'px';
|
||||
this.selection.style.left = startPosition + 'px';
|
||||
this.rightHandle.style.left = endPosition + 'px';
|
||||
this.selection.style.width = delta + 'px';
|
||||
}
|
||||
|
||||
get leftHandlePosX() {
|
||||
return this.leftHandle.getBoundingClientRect().x;
|
||||
}
|
||||
get leftHandlePosX() {
|
||||
return this.leftHandle.getBoundingClientRect().x;
|
||||
}
|
||||
|
||||
get rightHandlePosX() {
|
||||
return this.rightHandle.getBoundingClientRect().x;
|
||||
}
|
||||
get rightHandlePosX() {
|
||||
return this.rightHandle.getBoundingClientRect().x;
|
||||
}
|
||||
|
||||
// Maps the clicked x position to the x position on timeline canvas
|
||||
positionOnTimeline(posX) {
|
||||
let rect = this.timeline.getBoundingClientRect();
|
||||
let posClickedX = posX - rect.left + this.timeline.scrollLeft;
|
||||
return posClickedX;
|
||||
}
|
||||
// Maps the clicked x position to the x position on timeline canvas
|
||||
positionOnTimeline(posX) {
|
||||
let rect = this.timeline.getBoundingClientRect();
|
||||
let posClickedX = posX - rect.left + this.timeline.scrollLeft;
|
||||
return posClickedX;
|
||||
}
|
||||
|
||||
positionToTime(posX) {
|
||||
let posTimelineX = this.positionOnTimeline(posX) + this._timeStartOffset;
|
||||
return posTimelineX / this._timeToPixel;
|
||||
}
|
||||
positionToTime(posX) {
|
||||
let posTimelineX = this.positionOnTimeline(posX) + this._timeStartOffset;
|
||||
return posTimelineX / this._timeToPixel;
|
||||
}
|
||||
|
||||
timeToPosition(time) {
|
||||
let posX = time * this._timeToPixel;
|
||||
posX -= this._timeStartOffset
|
||||
return posX;
|
||||
}
|
||||
timeToPosition(time) {
|
||||
let posX = time * this._timeToPixel;
|
||||
posX -= this._timeStartOffset
|
||||
return posX;
|
||||
}
|
||||
|
||||
get leftHandle() {
|
||||
return this.$('.leftHandle');
|
||||
}
|
||||
get leftHandle() {
|
||||
return this.$('.leftHandle');
|
||||
}
|
||||
|
||||
get rightHandle() {
|
||||
return this.$('.rightHandle');
|
||||
}
|
||||
get rightHandle() {
|
||||
return this.$('.rightHandle');
|
||||
}
|
||||
|
||||
get selection() {
|
||||
return this.$('.selection');
|
||||
}
|
||||
get selection() {
|
||||
return this.$('.selection');
|
||||
}
|
||||
|
||||
get timelineCanvas() {
|
||||
return this.$('#timelineCanvas');
|
||||
}
|
||||
get timelineCanvas() {
|
||||
return this.$('#timelineCanvas');
|
||||
}
|
||||
|
||||
get timelineChunks() {
|
||||
return this.$('#timelineChunks');
|
||||
}
|
||||
get timelineChunks() {
|
||||
return this.$('#timelineChunks');
|
||||
}
|
||||
|
||||
get timeline() {
|
||||
return this.$('#timeline');
|
||||
}
|
||||
get timeline() {
|
||||
return this.$('#timeline');
|
||||
}
|
||||
|
||||
get timelineLegend() {
|
||||
return this.$('#legend');
|
||||
}
|
||||
get timelineLegend() {
|
||||
return this.$('#legend');
|
||||
}
|
||||
|
||||
get timelineLegendContent() {
|
||||
return this.$('#legendContent');
|
||||
}
|
||||
get timelineLegendContent() {
|
||||
return this.$('#legendContent');
|
||||
}
|
||||
|
||||
set data(value) {
|
||||
this._timeline = value;
|
||||
this._resetTypeToColorCache();
|
||||
this.updateChunks();
|
||||
this.updateTimeline();
|
||||
this.renderLegend();
|
||||
}
|
||||
set data(value) {
|
||||
this._timeline = value;
|
||||
this._resetTypeToColorCache();
|
||||
this.updateChunks();
|
||||
this.updateTimeline();
|
||||
this.renderLegend();
|
||||
}
|
||||
|
||||
_resetTypeToColorCache() {
|
||||
this._typeToColor = new Map();
|
||||
let lastIndex = 0;
|
||||
for (const type of this.data.uniqueTypes.keys()) {
|
||||
this._typeToColor.set(type, kColors[lastIndex++]);
|
||||
}
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this._timeline;
|
||||
}
|
||||
|
||||
set nofChunks(count) {
|
||||
this._nofChunks = count;
|
||||
this.updateChunks();
|
||||
this.updateTimeline();
|
||||
}
|
||||
|
||||
get nofChunks() {
|
||||
return this._nofChunks;
|
||||
}
|
||||
|
||||
updateChunks() {
|
||||
this._chunks = this.data.chunks(this.nofChunks);
|
||||
}
|
||||
|
||||
get chunks() {
|
||||
return this._chunks;
|
||||
}
|
||||
|
||||
set selectedEntry(value) {
|
||||
this._selectedEntry = value;
|
||||
if (value.edge) this.redraw();
|
||||
}
|
||||
|
||||
get selectedEntry() {
|
||||
return this._selectedEntry;
|
||||
}
|
||||
|
||||
set scrollLeft(offset) {
|
||||
this.timeline.scrollLeft = offset;
|
||||
}
|
||||
|
||||
typeToColor(type) {
|
||||
return this._typeToColor.get(type);
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
let timelineLegend = this.timelineLegend;
|
||||
let timelineLegendContent = this.timelineLegendContent;
|
||||
DOM.removeAllChildren(timelineLegendContent);
|
||||
this._timeline.uniqueTypes.forEach((entries, type) => {
|
||||
let row = DOM.tr("clickable");
|
||||
row.entries = entries;
|
||||
row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
|
||||
let color = this.typeToColor(type);
|
||||
if (color !== null) {
|
||||
let div = DOM.div("colorbox");
|
||||
div.style.backgroundColor = color;
|
||||
row.appendChild(DOM.td(div));
|
||||
} else {
|
||||
row.appendChild(DOM.td());
|
||||
}
|
||||
let td = DOM.td(type);
|
||||
row.appendChild(td);
|
||||
row.appendChild(DOM.td(entries.length));
|
||||
let percent = (entries.length / this.data.all.length) * 100;
|
||||
row.appendChild(DOM.td(percent.toFixed(1) + "%"));
|
||||
timelineLegendContent.appendChild(row);
|
||||
});
|
||||
// Add Total row.
|
||||
let row = DOM.tr();
|
||||
row.appendChild(DOM.td(""));
|
||||
row.appendChild(DOM.td("All"));
|
||||
row.appendChild(DOM.td(this.data.all.length));
|
||||
row.appendChild(DOM.td("100%"));
|
||||
timelineLegendContent.appendChild(row);
|
||||
timelineLegend.appendChild(timelineLegendContent);
|
||||
}
|
||||
|
||||
handleEntryTypeDblClick(e) {
|
||||
this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries));
|
||||
}
|
||||
|
||||
timelineIndicatorMove(offset) {
|
||||
this.timeline.scrollLeft += offset;
|
||||
}
|
||||
|
||||
handleTimelineScroll(e) {
|
||||
let horizontal = e.currentTarget.scrollLeft;
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'scrolltrack', {
|
||||
bubbles: true, composed: true,
|
||||
detail: horizontal
|
||||
}));
|
||||
}
|
||||
|
||||
async setChunkBackgrounds(backgroundTodo) {
|
||||
const kMaxDuration = 50;
|
||||
let lastTime = 0;
|
||||
for (let [chunk, node] of backgroundTodo) {
|
||||
const current = performance.now();
|
||||
if (current - lastTime > kMaxDuration) {
|
||||
await delay(25);
|
||||
lastTime = current;
|
||||
}
|
||||
this.setChunkBackground(chunk, node);
|
||||
}
|
||||
}
|
||||
|
||||
setChunkBackground(chunk, node) {
|
||||
// Render the types of transitions as bar charts
|
||||
const kHeight = chunk.height;
|
||||
const kWidth = 1;
|
||||
this.backgroundCanvas.width = kWidth;
|
||||
this.backgroundCanvas.height = kHeight;
|
||||
let ctx = this.backgroundCanvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, kWidth, kHeight);
|
||||
let y = 0;
|
||||
let total = chunk.size();
|
||||
let type, count;
|
||||
if (true) {
|
||||
chunk.getBreakdown(map => map.type).forEach(([type, count]) => {
|
||||
ctx.fillStyle = this.typeToColor(type);
|
||||
let height = count / total * kHeight;
|
||||
ctx.fillRect(0, y, kWidth, y + height);
|
||||
y += height;
|
||||
});
|
||||
} else {
|
||||
chunk.items.forEach(map => {
|
||||
ctx.fillStyle = this.typeToColor(map.type);
|
||||
let y = chunk.yOffset(map);
|
||||
ctx.fillRect(0, y, kWidth, y + 1);
|
||||
});
|
||||
}
|
||||
|
||||
let imageData = this.backgroundCanvas.toDataURL('image/webp', 0.2);
|
||||
node.style.backgroundImage = 'url(' + imageData + ')';
|
||||
}
|
||||
|
||||
updateTimeline() {
|
||||
let chunksNode = this.timelineChunks;
|
||||
DOM.removeAllChildren(chunksNode);
|
||||
let chunks = this.chunks;
|
||||
let max = chunks.max(each => each.size());
|
||||
let start = this.data.startTime;
|
||||
let end = this.data.endTime;
|
||||
let duration = end - start;
|
||||
this._timeToPixel = chunks.length * kChunkWidth / duration;
|
||||
this._timeStartOffset = start * this._timeToPixel;
|
||||
let addTimestamp = (time, name) => {
|
||||
let timeNode = DOM.div('timestamp');
|
||||
timeNode.innerText = name;
|
||||
timeNode.style.left = ((time - start) * this._timeToPixel) + 'px';
|
||||
chunksNode.appendChild(timeNode);
|
||||
};
|
||||
let backgroundTodo = [];
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
let chunk = chunks[i];
|
||||
let height = (chunk.size() / max * kChunkHeight);
|
||||
chunk.height = height;
|
||||
if (chunk.isEmpty()) continue;
|
||||
let node = DOM.div();
|
||||
node.className = 'chunk';
|
||||
node.style.left =
|
||||
((chunks[i].start - start) * this._timeToPixel) + 'px';
|
||||
node.style.height = height + 'px';
|
||||
node.chunk = chunk;
|
||||
node.addEventListener('mousemove', e => this.handleChunkMouseMove(e));
|
||||
node.addEventListener('click', e => this.handleChunkClick(e));
|
||||
node.addEventListener('dblclick', e => this.handleChunkDoubleClick(e));
|
||||
backgroundTodo.push([chunk, node])
|
||||
chunksNode.appendChild(node);
|
||||
}
|
||||
this.setChunkBackgrounds(backgroundTodo);
|
||||
|
||||
// Put a time marker roughly every 20 chunks.
|
||||
let expected = duration / chunks.length * 20;
|
||||
let interval = (10 ** Math.floor(Math.log10(expected)));
|
||||
let correction = Math.log10(expected / interval);
|
||||
correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5;
|
||||
interval *= correction;
|
||||
|
||||
let time = start;
|
||||
while (time < end) {
|
||||
addTimestamp(time, ((time - start) / 1000) + ' ms');
|
||||
time += interval;
|
||||
}
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
handleChunkMouseMove(event) {
|
||||
if (this.isLocked) return false;
|
||||
if (this._isSelecting) return false;
|
||||
let chunk = event.target.chunk;
|
||||
if (!chunk) return;
|
||||
// topmost map (at chunk.height) == map #0.
|
||||
let relativeIndex =
|
||||
Math.round(event.layerY / event.target.offsetHeight * chunk.size());
|
||||
let map = chunk.at(relativeIndex);
|
||||
this.dispatchEvent(new FocusEvent(map));
|
||||
}
|
||||
|
||||
handleChunkClick(event) {
|
||||
this.isLocked = !this.isLocked;
|
||||
}
|
||||
|
||||
handleChunkDoubleClick(event) {
|
||||
let chunk = event.target.chunk;
|
||||
if (!chunk) return;
|
||||
this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end));
|
||||
}
|
||||
|
||||
redraw() {
|
||||
let canvas = this.timelineCanvas;
|
||||
canvas.width = (this.chunks.length + 1) * kChunkWidth;
|
||||
canvas.height = kChunkHeight;
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, kChunkHeight);
|
||||
if (!this.selectedEntry || !this.selectedEntry.edge) return;
|
||||
this.drawEdges(ctx);
|
||||
}
|
||||
setMapStyle(map, ctx) {
|
||||
ctx.fillStyle = map.edge && map.edge.from ?
|
||||
CSSColor.onBackgroundColor : CSSColor.onPrimaryColor;
|
||||
}
|
||||
|
||||
setEdgeStyle(edge, ctx) {
|
||||
let color = this.typeToColor(edge.type);
|
||||
ctx.strokeStyle = color;
|
||||
ctx.fillStyle = color;
|
||||
}
|
||||
|
||||
markMap(ctx, map) {
|
||||
let [x, y] = map.position(this.chunks);
|
||||
ctx.beginPath();
|
||||
this.setMapStyle(map, ctx);
|
||||
ctx.arc(x, y, 3, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = CSSColor.onBackgroundColor;
|
||||
ctx.arc(x, y, 2, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
markSelectedMap(ctx, map) {
|
||||
let [x, y] = map.position(this.chunks);
|
||||
ctx.beginPath();
|
||||
this.setMapStyle(map, ctx);
|
||||
ctx.arc(x, y, 6, 0, 2 * Math.PI);
|
||||
ctx.strokeStyle = CSSColor.onBackgroundColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
drawEdges(ctx) {
|
||||
// Draw the trace of maps in reverse order to make sure the outgoing
|
||||
// transitions of previous maps aren't drawn over.
|
||||
const kMaxOutgoingEdges = 100;
|
||||
let nofEdges = 0;
|
||||
let stack = [];
|
||||
let current = this.selectedEntry;
|
||||
while (current && nofEdges < kMaxOutgoingEdges) {
|
||||
nofEdges += current.children.length;
|
||||
stack.push(current);
|
||||
current = current.parent();
|
||||
}
|
||||
ctx.save();
|
||||
this.drawOutgoingEdges(ctx, this.selectedEntry, 3);
|
||||
ctx.restore();
|
||||
|
||||
let labelOffset = 15;
|
||||
let xPrev = 0;
|
||||
while (current = stack.pop()) {
|
||||
if (current.edge) {
|
||||
this.setEdgeStyle(current.edge, ctx);
|
||||
let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset);
|
||||
if (xTo == xPrev) {
|
||||
labelOffset += 8;
|
||||
} else {
|
||||
labelOffset = 15
|
||||
}
|
||||
xPrev = xTo;
|
||||
}
|
||||
this.markMap(ctx, current);
|
||||
current = current.parent();
|
||||
ctx.save();
|
||||
// this.drawOutgoingEdges(ctx, current, 1);
|
||||
ctx.restore();
|
||||
}
|
||||
// Mark selected map
|
||||
this.markSelectedMap(ctx, this.selectedEntry);
|
||||
}
|
||||
|
||||
drawEdge(ctx, edge, showLabel = true, labelOffset = 20) {
|
||||
if (!edge.from || !edge.to) return [-1, -1];
|
||||
let [xFrom, yFrom] = edge.from.position(this.chunks);
|
||||
let [xTo, yTo] = edge.to.position(this.chunks);
|
||||
let sameChunk = xTo == xFrom;
|
||||
if (sameChunk) labelOffset += 8;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xFrom, yFrom);
|
||||
let offsetX = 20;
|
||||
let offsetY = 20;
|
||||
let midX = xFrom + (xTo - xFrom) / 2;
|
||||
let midY = (yFrom + yTo) / 2 - 100;
|
||||
if (!sameChunk) {
|
||||
ctx.quadraticCurveTo(midX, midY, xTo, yTo);
|
||||
} else {
|
||||
ctx.lineTo(xTo, yTo);
|
||||
}
|
||||
if (!showLabel) {
|
||||
ctx.stroke();
|
||||
} else {
|
||||
let centerX, centerY;
|
||||
if (!sameChunk) {
|
||||
centerX = (xFrom / 2 + midX + xTo / 2) / 2;
|
||||
centerY = (yFrom / 2 + midY + yTo / 2) / 2;
|
||||
} else {
|
||||
centerX = xTo;
|
||||
centerY = yTo;
|
||||
}
|
||||
ctx.moveTo(centerX, centerY);
|
||||
ctx.lineTo(centerX + offsetX, centerY - labelOffset);
|
||||
ctx.stroke();
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillStyle = this.typeToColor(edge.type);
|
||||
ctx.fillText(
|
||||
edge.toString(), centerX + offsetX + 2, centerY - labelOffset);
|
||||
}
|
||||
return [xTo, yTo];
|
||||
}
|
||||
|
||||
drawOutgoingEdges(ctx, map, max = 10, depth = 0) {
|
||||
if (!map) return;
|
||||
if (depth >= max) return;
|
||||
ctx.globalAlpha = 0.5 - depth * (0.3 / max);
|
||||
ctx.strokeStyle = CSSColor.timelineBackgroundColor;
|
||||
const limit = Math.min(map.children.length, 100)
|
||||
for (let i = 0; i < limit; i++) {
|
||||
let edge = map.children[i];
|
||||
this.drawEdge(ctx, edge, true);
|
||||
this.drawOutgoingEdges(ctx, edge.to, max, depth + 1);
|
||||
}
|
||||
_resetTypeToColorCache() {
|
||||
this._typeToColor = new Map();
|
||||
let lastIndex = 0;
|
||||
for (const type of this.data.uniqueTypes.keys()) {
|
||||
this._typeToColor.set(type, kColors[lastIndex++]);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
get data() {
|
||||
return this._timeline;
|
||||
}
|
||||
|
||||
set nofChunks(count) {
|
||||
this._nofChunks = count;
|
||||
this.updateChunks();
|
||||
this.updateTimeline();
|
||||
}
|
||||
|
||||
get nofChunks() {
|
||||
return this._nofChunks;
|
||||
}
|
||||
|
||||
updateChunks() {
|
||||
this._chunks = this.data.chunks(this.nofChunks);
|
||||
}
|
||||
|
||||
get chunks() {
|
||||
return this._chunks;
|
||||
}
|
||||
|
||||
set selectedEntry(value) {
|
||||
this._selectedEntry = value;
|
||||
if (value.edge) this.redraw();
|
||||
}
|
||||
|
||||
get selectedEntry() {
|
||||
return this._selectedEntry;
|
||||
}
|
||||
|
||||
set scrollLeft(offset) {
|
||||
this.timeline.scrollLeft = offset;
|
||||
}
|
||||
|
||||
typeToColor(type) {
|
||||
return this._typeToColor.get(type);
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
let timelineLegend = this.timelineLegend;
|
||||
let timelineLegendContent = this.timelineLegendContent;
|
||||
DOM.removeAllChildren(timelineLegendContent);
|
||||
this._timeline.uniqueTypes.forEach((entries, type) => {
|
||||
let row = DOM.tr('clickable');
|
||||
row.entries = entries;
|
||||
row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
|
||||
let color = this.typeToColor(type);
|
||||
if (color !== null) {
|
||||
let div = DOM.div('colorbox');
|
||||
div.style.backgroundColor = color;
|
||||
row.appendChild(DOM.td(div));
|
||||
} else {
|
||||
row.appendChild(DOM.td());
|
||||
}
|
||||
let td = DOM.td(type);
|
||||
row.appendChild(td);
|
||||
row.appendChild(DOM.td(entries.length));
|
||||
let percent = (entries.length / this.data.all.length) * 100;
|
||||
row.appendChild(DOM.td(percent.toFixed(1) + '%'));
|
||||
timelineLegendContent.appendChild(row);
|
||||
});
|
||||
// Add Total row.
|
||||
let row = DOM.tr();
|
||||
row.appendChild(DOM.td(''));
|
||||
row.appendChild(DOM.td('All'));
|
||||
row.appendChild(DOM.td(this.data.all.length));
|
||||
row.appendChild(DOM.td('100%'));
|
||||
timelineLegendContent.appendChild(row);
|
||||
timelineLegend.appendChild(timelineLegendContent);
|
||||
}
|
||||
|
||||
handleEntryTypeDblClick(e) {
|
||||
this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries));
|
||||
}
|
||||
|
||||
timelineIndicatorMove(offset) {
|
||||
this.timeline.scrollLeft += offset;
|
||||
}
|
||||
|
||||
handleTimelineScroll(e) {
|
||||
let horizontal = e.currentTarget.scrollLeft;
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'scrolltrack', {bubbles: true, composed: true, detail: horizontal}));
|
||||
}
|
||||
|
||||
async setChunkBackgrounds(backgroundTodo) {
|
||||
const kMaxDuration = 50;
|
||||
let lastTime = 0;
|
||||
for (let [chunk, node] of backgroundTodo) {
|
||||
const current = performance.now();
|
||||
if (current - lastTime > kMaxDuration) {
|
||||
await delay(25);
|
||||
lastTime = current;
|
||||
}
|
||||
this.setChunkBackground(chunk, node);
|
||||
}
|
||||
}
|
||||
|
||||
setChunkBackground(chunk, node) {
|
||||
// Render the types of transitions as bar charts
|
||||
const kHeight = chunk.height;
|
||||
const kWidth = 1;
|
||||
this.backgroundCanvas.width = kWidth;
|
||||
this.backgroundCanvas.height = kHeight;
|
||||
let ctx = this.backgroundCanvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, kWidth, kHeight);
|
||||
let y = 0;
|
||||
let total = chunk.size();
|
||||
let type, count;
|
||||
if (true) {
|
||||
chunk.getBreakdown(map => map.type).forEach(([type, count]) => {
|
||||
ctx.fillStyle = this.typeToColor(type);
|
||||
let height = count / total * kHeight;
|
||||
ctx.fillRect(0, y, kWidth, y + height);
|
||||
y += height;
|
||||
});
|
||||
} else {
|
||||
chunk.items.forEach(map => {
|
||||
ctx.fillStyle = this.typeToColor(map.type);
|
||||
let y = chunk.yOffset(map);
|
||||
ctx.fillRect(0, y, kWidth, y + 1);
|
||||
});
|
||||
}
|
||||
|
||||
let imageData = this.backgroundCanvas.toDataURL('image/webp', 0.2);
|
||||
node.style.backgroundImage = 'url(' + imageData + ')';
|
||||
}
|
||||
|
||||
updateTimeline() {
|
||||
let chunksNode = this.timelineChunks;
|
||||
DOM.removeAllChildren(chunksNode);
|
||||
let chunks = this.chunks;
|
||||
let max = chunks.max(each => each.size());
|
||||
let start = this.data.startTime;
|
||||
let end = this.data.endTime;
|
||||
let duration = end - start;
|
||||
this._timeToPixel = chunks.length * kChunkWidth / duration;
|
||||
this._timeStartOffset = start * this._timeToPixel;
|
||||
let addTimestamp = (time, name) => {
|
||||
let timeNode = DOM.div('timestamp');
|
||||
timeNode.innerText = name;
|
||||
timeNode.style.left = ((time - start) * this._timeToPixel) + 'px';
|
||||
chunksNode.appendChild(timeNode);
|
||||
};
|
||||
let backgroundTodo = [];
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
let chunk = chunks[i];
|
||||
let height = (chunk.size() / max * kChunkHeight);
|
||||
chunk.height = height;
|
||||
if (chunk.isEmpty()) continue;
|
||||
let node = DOM.div();
|
||||
node.className = 'chunk';
|
||||
node.style.left = ((chunks[i].start - start) * this._timeToPixel) + 'px';
|
||||
node.style.height = height + 'px';
|
||||
node.chunk = chunk;
|
||||
node.addEventListener('mousemove', e => this.handleChunkMouseMove(e));
|
||||
node.addEventListener('click', e => this.handleChunkClick(e));
|
||||
node.addEventListener('dblclick', e => this.handleChunkDoubleClick(e));
|
||||
backgroundTodo.push([chunk, node])
|
||||
chunksNode.appendChild(node);
|
||||
}
|
||||
this.setChunkBackgrounds(backgroundTodo);
|
||||
|
||||
// Put a time marker roughly every 20 chunks.
|
||||
let expected = duration / chunks.length * 20;
|
||||
let interval = (10 ** Math.floor(Math.log10(expected)));
|
||||
let correction = Math.log10(expected / interval);
|
||||
correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5;
|
||||
interval *= correction;
|
||||
|
||||
let time = start;
|
||||
while (time < end) {
|
||||
addTimestamp(time, ((time - start) / 1000) + ' ms');
|
||||
time += interval;
|
||||
}
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
handleChunkMouseMove(event) {
|
||||
if (this.isLocked) return false;
|
||||
if (this._isSelecting) return false;
|
||||
let chunk = event.target.chunk;
|
||||
if (!chunk) return;
|
||||
// topmost map (at chunk.height) == map #0.
|
||||
let relativeIndex =
|
||||
Math.round(event.layerY / event.target.offsetHeight * chunk.size());
|
||||
let map = chunk.at(relativeIndex);
|
||||
this.dispatchEvent(new FocusEvent(map));
|
||||
}
|
||||
|
||||
handleChunkClick(event) {
|
||||
this.isLocked = !this.isLocked;
|
||||
}
|
||||
|
||||
handleChunkDoubleClick(event) {
|
||||
let chunk = event.target.chunk;
|
||||
if (!chunk) return;
|
||||
this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end));
|
||||
}
|
||||
|
||||
redraw() {
|
||||
let canvas = this.timelineCanvas;
|
||||
canvas.width = (this.chunks.length + 1) * kChunkWidth;
|
||||
canvas.height = kChunkHeight;
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, kChunkHeight);
|
||||
if (!this.selectedEntry || !this.selectedEntry.edge) return;
|
||||
this.drawEdges(ctx);
|
||||
}
|
||||
setMapStyle(map, ctx) {
|
||||
ctx.fillStyle = map.edge && map.edge.from ? CSSColor.onBackgroundColor :
|
||||
CSSColor.onPrimaryColor;
|
||||
}
|
||||
|
||||
setEdgeStyle(edge, ctx) {
|
||||
let color = this.typeToColor(edge.type);
|
||||
ctx.strokeStyle = color;
|
||||
ctx.fillStyle = color;
|
||||
}
|
||||
|
||||
markMap(ctx, map) {
|
||||
let [x, y] = map.position(this.chunks);
|
||||
ctx.beginPath();
|
||||
this.setMapStyle(map, ctx);
|
||||
ctx.arc(x, y, 3, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = CSSColor.onBackgroundColor;
|
||||
ctx.arc(x, y, 2, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
markSelectedMap(ctx, map) {
|
||||
let [x, y] = map.position(this.chunks);
|
||||
ctx.beginPath();
|
||||
this.setMapStyle(map, ctx);
|
||||
ctx.arc(x, y, 6, 0, 2 * Math.PI);
|
||||
ctx.strokeStyle = CSSColor.onBackgroundColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
drawEdges(ctx) {
|
||||
// Draw the trace of maps in reverse order to make sure the outgoing
|
||||
// transitions of previous maps aren't drawn over.
|
||||
const kMaxOutgoingEdges = 100;
|
||||
let nofEdges = 0;
|
||||
let stack = [];
|
||||
let current = this.selectedEntry;
|
||||
while (current && nofEdges < kMaxOutgoingEdges) {
|
||||
nofEdges += current.children.length;
|
||||
stack.push(current);
|
||||
current = current.parent();
|
||||
}
|
||||
ctx.save();
|
||||
this.drawOutgoingEdges(ctx, this.selectedEntry, 3);
|
||||
ctx.restore();
|
||||
|
||||
let labelOffset = 15;
|
||||
let xPrev = 0;
|
||||
while (current = stack.pop()) {
|
||||
if (current.edge) {
|
||||
this.setEdgeStyle(current.edge, ctx);
|
||||
let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset);
|
||||
if (xTo == xPrev) {
|
||||
labelOffset += 8;
|
||||
} else {
|
||||
labelOffset = 15
|
||||
}
|
||||
xPrev = xTo;
|
||||
}
|
||||
this.markMap(ctx, current);
|
||||
current = current.parent();
|
||||
ctx.save();
|
||||
// this.drawOutgoingEdges(ctx, current, 1);
|
||||
ctx.restore();
|
||||
}
|
||||
// Mark selected map
|
||||
this.markSelectedMap(ctx, this.selectedEntry);
|
||||
}
|
||||
|
||||
drawEdge(ctx, edge, showLabel = true, labelOffset = 20) {
|
||||
if (!edge.from || !edge.to) return [-1, -1];
|
||||
let [xFrom, yFrom] = edge.from.position(this.chunks);
|
||||
let [xTo, yTo] = edge.to.position(this.chunks);
|
||||
let sameChunk = xTo == xFrom;
|
||||
if (sameChunk) labelOffset += 8;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xFrom, yFrom);
|
||||
let offsetX = 20;
|
||||
let offsetY = 20;
|
||||
let midX = xFrom + (xTo - xFrom) / 2;
|
||||
let midY = (yFrom + yTo) / 2 - 100;
|
||||
if (!sameChunk) {
|
||||
ctx.quadraticCurveTo(midX, midY, xTo, yTo);
|
||||
} else {
|
||||
ctx.lineTo(xTo, yTo);
|
||||
}
|
||||
if (!showLabel) {
|
||||
ctx.stroke();
|
||||
} else {
|
||||
let centerX, centerY;
|
||||
if (!sameChunk) {
|
||||
centerX = (xFrom / 2 + midX + xTo / 2) / 2;
|
||||
centerY = (yFrom / 2 + midY + yTo / 2) / 2;
|
||||
} else {
|
||||
centerX = xTo;
|
||||
centerY = yTo;
|
||||
}
|
||||
ctx.moveTo(centerX, centerY);
|
||||
ctx.lineTo(centerX + offsetX, centerY - labelOffset);
|
||||
ctx.stroke();
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillStyle = this.typeToColor(edge.type);
|
||||
ctx.fillText(
|
||||
edge.toString(), centerX + offsetX + 2, centerY - labelOffset);
|
||||
}
|
||||
return [xTo, yTo];
|
||||
}
|
||||
|
||||
drawOutgoingEdges(ctx, map, max = 10, depth = 0) {
|
||||
if (!map) return;
|
||||
if (depth >= max) return;
|
||||
ctx.globalAlpha = 0.5 - depth * (0.3 / max);
|
||||
ctx.strokeStyle = CSSColor.timelineBackgroundColor;
|
||||
const limit = Math.min(map.children.length, 100)
|
||||
for (let i = 0; i < limit; i++) {
|
||||
let edge = map.children[i];
|
||||
this.drawEdge(ctx, edge, true);
|
||||
this.drawOutgoingEdges(ctx, edge.to, max, depth + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -131,6 +131,31 @@ def TorqueLintWorker(command):
|
||||
print('Error running format-torque.py')
|
||||
process.kill()
|
||||
|
||||
def JSLintWorker(command):
|
||||
try:
|
||||
file_name = command[-1]
|
||||
with open(file_name, "r") as file_handle:
|
||||
contents = file_handle.read()
|
||||
|
||||
process = subprocess.Popen(command, stdout=PIPE, stderr=subprocess.PIPE)
|
||||
output, err = process.communicate()
|
||||
rc = process.returncode
|
||||
if rc != 0:
|
||||
sys.stdout.write("error code " + str(rc) + " running clang-format.\n")
|
||||
return rc
|
||||
|
||||
if output != contents:
|
||||
sys.stdout.write(file_name + " requires formatting.\n")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
except KeyboardInterrupt:
|
||||
process.kill()
|
||||
except Exception:
|
||||
print('Error running clang-format. Please make sure you have depot_tools' +
|
||||
' in your $PATH. Lint check skipped.')
|
||||
process.kill()
|
||||
|
||||
class FileContentsCache(object):
|
||||
|
||||
def __init__(self, sums_file_name):
|
||||
@ -392,6 +417,33 @@ class TorqueLintProcessor(CacheableSourceFileProcessor):
|
||||
|
||||
return None, arguments
|
||||
|
||||
class JSLintProcessor(CacheableSourceFileProcessor):
|
||||
"""
|
||||
Check .{m}js file to verify they follow the JS Style guide.
|
||||
"""
|
||||
def __init__(self, use_cache=True):
|
||||
super(JSLintProcessor, self).__init__(
|
||||
use_cache=use_cache, cache_file_path='.jslint-cache',
|
||||
file_type='JavaScript')
|
||||
|
||||
def IsRelevant(self, name):
|
||||
return name.endswith('.js') or name.endswith('.mjs')
|
||||
|
||||
def GetPathsToSearch(self):
|
||||
return ['tools/system-analyzer']
|
||||
|
||||
def GetProcessorWorker(self):
|
||||
return JSLintWorker
|
||||
|
||||
def GetProcessorScript(self):
|
||||
for path in [TOOLS_PATH] + os.environ["PATH"].split(os.pathsep):
|
||||
path = path.strip('"')
|
||||
clang_format = os.path.join(path, 'clang_format.py')
|
||||
if os.path.isfile(clang_format):
|
||||
return clang_format, []
|
||||
|
||||
return None, []
|
||||
|
||||
COPYRIGHT_HEADER_PATTERN = re.compile(
|
||||
r'Copyright [\d-]*20[0-2][0-9] the V8 project authors. All rights reserved.')
|
||||
|
||||
@ -708,6 +760,9 @@ def Main():
|
||||
print("Running Torque formatting check...")
|
||||
success &= TorqueLintProcessor(use_cache=use_linter_cache).RunOnPath(
|
||||
workspace)
|
||||
print("Running JavaScript formatting check...")
|
||||
success &= JSLintProcessor(use_cache=use_linter_cache).RunOnPath(
|
||||
workspace)
|
||||
print("Running copyright header, trailing whitespaces and " \
|
||||
"two empty lines between declarations check...")
|
||||
success &= SourceProcessor().RunOnPath(workspace)
|
||||
|
Loading…
Reference in New Issue
Block a user