diff --git a/tools/profile.js b/tools/profile.js
index 991349a348..0439bb9eed 100644
--- a/tools/profile.js
+++ b/tools/profile.js
@@ -38,6 +38,8 @@ function Profile() {
this.bottomUpTree_ = new CallTree();
this.c_entries_ = {};
this.ticks_ = [];
+ this.scripts_ = [];
+ this.urlToScript_ = new Map();
};
@@ -226,8 +228,21 @@ Profile.prototype.addSourcePositions = function (
/**
* Adds script source code.
*/
-Profile.prototype.addScriptSource = function (script, source) {
- // CLI does not need source code => ignore.
+Profile.prototype.addScriptSource = function (scriptId, url, source) {
+ this.scripts_[scriptId] = {
+ scriptId: scriptId,
+ name: url,
+ source: source
+ };
+ this.urlToScript_.set(url, source);
+};
+
+
+/**
+ * Adds script source code.
+ */
+Profile.prototype.getScript = function (url) {
+ return this.urlToScript_.get(url);
};
/**
@@ -1018,8 +1033,9 @@ JsonProfile.prototype.addSourcePositions = function (
};
};
-JsonProfile.prototype.addScriptSource = function (script, url, source) {
- this.scripts_[script] = {
+JsonProfile.prototype.addScriptSource = function (scriptId, url, source) {
+ this.scripts_[scriptId] = {
+ scriptId: scriptId,
name: url,
source: source
};
diff --git a/tools/system-analyzer/app-model.mjs b/tools/system-analyzer/app-model.mjs
index 821d3a2c17..37fa5ae2f3 100644
--- a/tools/system-analyzer/app-model.mjs
+++ b/tools/system-analyzer/app-model.mjs
@@ -8,6 +8,7 @@ class State {
#ic;
#selectedMapLogEvents;
#selectedIcLogEvents;
+ #selectedSourcePositionLogEvents;
#nofChunks;
#chunks;
#icTimeline;
@@ -81,6 +82,12 @@ class State {
if (!value) return;
this.#selectedMapLogEvents = value;
}
+ get selectedSourcePositionLogEvents() {
+ return this.#selectedSourcePositionLogEvents;
+ }
+ set selectedSourcePositionLogEvents(value) {
+ this.#selectedSourcePositionLogEvents = value;
+ }
get selectedIcLogEvents() {
return this.#selectedIcLogEvents;
}
diff --git a/tools/system-analyzer/event.mjs b/tools/system-analyzer/event.mjs
index f3af966a23..438fc1e015 100644
--- a/tools/system-analyzer/event.mjs
+++ b/tools/system-analyzer/event.mjs
@@ -18,4 +18,14 @@ class Event {
}
}
-export { Event };
+class SourcePositionLogEvent extends Event {
+ constructor(type, time, file, line, col, script) {
+ super(type, time);
+ this.file = file;
+ this.line = line;
+ this.col = col;
+ this.script = script;
+ }
+}
+
+export { Event, SourcePositionLogEvent };
diff --git a/tools/system-analyzer/ic-panel.mjs b/tools/system-analyzer/ic-panel.mjs
index 6f0e0cb558..d97f66022a 100644
--- a/tools/system-analyzer/ic-panel.mjs
+++ b/tools/system-analyzer/ic-panel.mjs
@@ -5,12 +5,12 @@
import { Group } from './ic-model.mjs';
import CustomIcProcessor from "./ic-processor.mjs";
import { MapLogEvent } from "./map-processor.mjs";
+import { SourcePositionLogEvent } from './event.mjs';
import { FocusEvent, SelectTimeEvent, SelectionEvent } from './events.mjs';
import { defineCustomElement, V8CustomElement } from './helper.mjs';
defineCustomElement('ic-panel', (templateText) =>
class ICPanel extends V8CustomElement {
- //TODO(zcankara) Entries never set
#selectedLogEvents;
#timeline;
constructor() {
@@ -29,8 +29,6 @@ defineCustomElement('ic-panel', (templateText) =>
this.selectedLogEvents = this.timeline.all;
this.updateCount();
}
-
-
get groupKey() {
return this.$('#group-key');
}
@@ -105,26 +103,38 @@ defineCustomElement('ic-panel', (templateText) =>
}
handleMapClick(e) {
- let entry = e.target.parentNode.entry;
- let id = entry.key;
- let selectedMapLofEvents =
+ const entry = e.target.parentNode.entry;
+ const id = entry.key;
+ const selectedMapLogEvents =
this.searchIcLogEventToMapLogEvent(id, entry.entries);
- this.dispatchEvent(new SelectionEvent(selectedMapLofEvents));
+ this.dispatchEvent(new SelectionEvent(selectedMapLogEvents));
}
searchIcLogEventToMapLogEvent(id, icLogEvents) {
// searches for mapLogEvents using the id, time
- let selectedMapLogEventsSet = new Set();
+ const selectedMapLogEventsSet = new Set();
for (const icLogEvent of icLogEvents) {
- let time = icLogEvent.time;
- let selectedMap = MapLogEvent.get(id, time);
+ const time = icLogEvent.time;
+ const selectedMap = MapLogEvent.get(id, time);
selectedMapLogEventsSet.add(selectedMap);
}
return Array.from(selectedMapLogEventsSet);
}
handleFilePositionClick(e) {
- this.dispatchEvent(new FocusEvent(e.target.parentNode.entry.key));
+ const entry = e.target.parentNode.entry;
+ const filePosition =
+ this.createSourcePositionLogEvent(
+ entry.entries[0].type, entry.entries[0].time, entry.key,
+ entry.entries[0].script);
+ this.dispatchEvent(new FocusEvent(filePosition));
+ }
+
+ createSourcePositionLogEvent(type, time, filePositionLine, script) {
+ const [file, line, col] = filePositionLine.split(':');
+ const filePosition = new SourcePositionLogEvent(type, time,
+ file, line, col, script);
+ return filePosition
}
render(entries, parent) {
diff --git a/tools/system-analyzer/ic-processor.mjs b/tools/system-analyzer/ic-processor.mjs
index 4de67f5cb9..9b14f770ff 100644
--- a/tools/system-analyzer/ic-processor.mjs
+++ b/tools/system-analyzer/ic-processor.mjs
@@ -44,6 +44,10 @@ class IcProcessor extends LogReader {
],
processor: this.processV8Version
},
+ 'script-source': {
+ parsers: [parseInt, parseString, parseString],
+ processor: this.processScriptSource
+ },
'code-move':
{ parsers: [parseInt, parseInt], processor: this.processCodeMove },
'code-delete': { parsers: [parseInt], processor: this.processCodeDelete },
@@ -122,6 +126,9 @@ class IcProcessor extends LogReader {
`Please use the matching tool for given the V8 version.`);
}
}
+ processScriptSource(scriptId, url, script) {
+ this.#profile.addScriptSource(scriptId, url, script);
+ }
processLogFile(fileName) {
this.collectEntries = true;
this.lastLogFileName_ = fileName;
@@ -181,6 +188,11 @@ class IcProcessor extends LogReader {
' (map 0x' + map.toString(16) + ')' +
(slow_reason ? ' ' + slow_reason : '') + 'time: ' + time);
}
+
+ getScript(url) {
+ return this.#profile.getScript(url);
+ }
+
}
// ================
@@ -209,9 +221,12 @@ class CustomIcProcessor extends IcProcessor {
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[1];
+ let script = this.getScript(fileName);
let entry = new IcLogEvent(
type, fnName, time, line, column, key, old_state, new_state, map,
- slow_reason);
+ slow_reason, script);
this.#timeline.push(entry);
}
@@ -229,7 +244,7 @@ class CustomIcProcessor extends IcProcessor {
class IcLogEvent extends Event {
constructor(
type, fn_file, time, line, column, key, oldState, newState, map, reason,
- additional) {
+ script, additional) {
super(type, time);
this.category = 'other';
if (this.type.indexOf('Store') !== -1) {
@@ -249,6 +264,7 @@ class IcLogEvent extends Event {
this.map = map;
this.reason = reason;
this.additional = additional;
+ this.script = script;
}
diff --git a/tools/system-analyzer/index.css b/tools/system-analyzer/index.css
index d57727950a..38476c6d48 100644
--- a/tools/system-analyzer/index.css
+++ b/tools/system-analyzer/index.css
@@ -135,6 +135,10 @@ button {
background-color: var(--error-color);
}
+.highlight {
+ background-color: var(--primary-color);
+ color: var(--on-primary-color);
+}
.clickable:hover,
.clickable:active {
background-color: var(--primary-color);
diff --git a/tools/system-analyzer/index.html b/tools/system-analyzer/index.html
index 6f5c483974..400e573f3f 100644
--- a/tools/system-analyzer/index.html
+++ b/tools/system-analyzer/index.html
@@ -115,9 +115,9 @@ found in the LICENSE file. -->
@@ -141,6 +141,7 @@ found in the LICENSE file. -->
+
diff --git a/tools/system-analyzer/index.mjs b/tools/system-analyzer/index.mjs
index e339bc1d13..4ead408cfa 100644
--- a/tools/system-analyzer/index.mjs
+++ b/tools/system-analyzer/index.mjs
@@ -8,17 +8,19 @@ import { IcLogEvent } from "./ic-processor.mjs";
import { State } from "./app-model.mjs";
import { MapProcessor, MapLogEvent } from "./map-processor.mjs";
import { SelectTimeEvent } from "./events.mjs";
+import { SourcePositionLogEvent } from "./event.mjs";
import { $ } from "./helper.mjs";
import "./ic-panel.mjs";
import "./timeline-panel.mjs";
import "./map-panel.mjs";
import "./log-file-reader.mjs";
+import "./source-panel.mjs";
class App {
#state;
#view;
#navigation;
constructor(fileReaderId, mapPanelId, timelinePanelId,
- icPanelId, mapTrackId, icTrackId) {
+ icPanelId, mapTrackId, icTrackId, sourcePanelId) {
this.#view = {
logFileReader: $(fileReaderId),
icPanel: $(icPanelId),
@@ -26,6 +28,7 @@ class App {
timelinePanel: $(timelinePanelId),
mapTrack: $(mapTrackId),
icTrack: $(icTrackId),
+ sourcePanel: $(sourcePanelId)
};
this.#state = new State();
this.#navigation = new Navigation(this.#state, this.#view);
@@ -53,6 +56,8 @@ class App {
this.showMapEntries(e.entries);
} else if (e.entries[0] instanceof IcLogEvent) {
this.showIcEntries(e.entries);
+ } else if (e.entries[0] instanceof SourcePositionLogEvent) {
+ this.showSourcePositionEntries(e.entries);
} else {
console.error("Undefined selection!");
}
@@ -65,6 +70,10 @@ class App {
this.#state.selectedIcLogEvents = entries;
this.#view.icPanel.selectedLogEvents = this.#state.selectedIcLogEvents;
}
+ showSourcePositionEntries(entries) {
+ //TODO(zcankara) Handle multiple source position selection events
+ this.#view.sourcePanel.selectedSourcePositions = entries;
+ }
handleTimeRangeSelect(e) {
this.selectTimeRange(e.start, e.end);
@@ -74,16 +83,12 @@ class App {
this.selectMapLogEvent(e.entry);
} else if (e.entry instanceof IcLogEvent) {
this.selectICLogEvent(e.entry);
- } else if (typeof e.entry === 'string') {
+ } else if (e.entry instanceof SourcePositionLogEvent) {
this.selectSourcePositionEvent(e.entry);
} else {
console.log("undefined");
}
}
- handleClickSourcePositions(e) {
- //TODO(zcankara) Handle source position
- console.log("Entry containing source position: ", e.entries);
- }
selectTimeRange(start, end) {
this.#state.timeSelection.start = start;
this.#state.timeSelection.end = end;
@@ -103,8 +108,11 @@ class App {
this.#view.icPanel.selectedLogEvents = [entry];
}
selectSourcePositionEvent(sourcePositions) {
+ if (!sourcePositions.script) return;
console.log("source positions: ", sourcePositions);
+ this.#view.sourcePanel.selectedSourcePositions = [sourcePositions];
}
+
handleFileUpload(e) {
this.restartApp();
$("#container").className = "initial";
diff --git a/tools/system-analyzer/map-panel/map-details-template.html b/tools/system-analyzer/map-panel/map-details-template.html
index b4b1541fd8..6d1b268c5d 100644
--- a/tools/system-analyzer/map-panel/map-details-template.html
+++ b/tools/system-analyzer/map-panel/map-details-template.html
@@ -6,7 +6,8 @@ found in the LICENSE file. -->
Map Details
+
diff --git a/tools/system-analyzer/map-panel/map-details.mjs b/tools/system-analyzer/map-panel/map-details.mjs
index dddccf7d8b..35a3443d67 100644
--- a/tools/system-analyzer/map-panel/map-details.mjs
+++ b/tools/system-analyzer/map-panel/map-details.mjs
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import { V8CustomElement, defineCustomElement } from "../helper.mjs";
import { FocusEvent } from "../events.mjs";
+import { SourcePositionLogEvent } from '../event.mjs';
defineCustomElement(
"./map-panel/map-details",
@@ -10,8 +11,8 @@ defineCustomElement(
class MapDetails extends V8CustomElement {
constructor() {
super(templateText);
- this.mapDetails.addEventListener("click", () =>
- this.handleClickSourcePositions()
+ this.#filePositionNode.addEventListener("click", e =>
+ this.handleFilePositionClick(e)
);
this.selectedMap = undefined;
}
@@ -19,23 +20,44 @@ defineCustomElement(
return this.$("#mapDetails");
}
+ get #filePositionNode() {
+ return this.$("#filePositionNode");
+ }
+
setSelectedMap(value) {
this.selectedMap = value;
}
set mapDetails(map) {
let details = "";
+ let clickableDetails = "";
if (map) {
- details += "ID: " + map.id;
- details += "\nSource location: " + map.filePosition;
+ 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.mapDetails.innerText = details;
}
- handleClickSourcePositions() {
- this.dispatchEvent(new FocusEvent(this.selectedMap.filePosition));
+ handleFilePositionClick() {
+ let filePosition =
+ this.createSourcePositionLogEvent(
+ this.selectedMap.type, this.selectedMap.time,
+ this.selectedMap.filePosition, this.selectedMap.script);
+ this.dispatchEvent(new FocusEvent(filePosition));
+ }
+
+ createSourcePositionLogEvent(type, time, filePositionLine, script) {
+ // remove token
+ if (!(/\s/.test(filePositionLine))) return;
+ filePositionLine = filePositionLine.split(' ');
+ let [file, line, col] = filePositionLine[1].split(':');
+ let filePosition = new SourcePositionLogEvent(type, time,
+ file, line, col, script);
+ return filePosition
}
}
);
diff --git a/tools/system-analyzer/map-processor.mjs b/tools/system-analyzer/map-processor.mjs
index 17a4b01112..04a47e4680 100644
--- a/tools/system-analyzer/map-processor.mjs
+++ b/tools/system-analyzer/map-processor.mjs
@@ -54,6 +54,10 @@ class MapProcessor extends LogReader {
],
processor: this.processV8Version
},
+ 'script-source': {
+ parsers: [parseInt, parseString, parseString],
+ processor: this.processScriptSource
+ },
'code-move': {
parsers: [parseInt, parseInt],
'sfi-move':
@@ -184,6 +188,10 @@ class MapProcessor extends LogReader {
}
}
+ processScriptSource(scriptId, url, source) {
+ this.#profile.addScriptSource(scriptId, url, source);
+ }
+
processCodeMove(from, to) {
this.#profile.moveCode(from, to);
}
@@ -211,6 +219,12 @@ class MapProcessor extends LogReader {
}
return entry + ':' + line + ':' + column;
}
+ processFileName(filePositionLine) {
+ if (!(/\s/.test(filePositionLine))) return;
+ filePositionLine = filePositionLine.split(' ');
+ let file = filePositionLine[1].split(':')[0];
+ return file;
+ }
processMap(type, time, from, to, pc, line, column, reason, name) {
let time_ = parseInt(time);
@@ -219,6 +233,8 @@ class MapProcessor extends LogReader {
let to_ = this.getExistingMap(to, time_);
let edge = new Edge(type, name, reason, time, from_, to_);
to_.filePosition = this.formatPC(pc, line, column);
+ let fileName = this.processFileName(to_.filePosition);
+ to_.script = this.getScript(fileName);
edge.finishSetup();
}
@@ -254,6 +270,10 @@ class MapProcessor extends LogReader {
};
return map;
}
+
+ getScript(url) {
+ return this.#profile.getScript(url);
+ }
}
// ===========================================================================
@@ -268,6 +288,7 @@ class MapLogEvent extends Event {
leftId = 0;
rightId = 0;
filePosition = '';
+ script = '';
id = -1;
constructor(id, time) {
if (!time) throw new Error('Invalid time');
diff --git a/tools/system-analyzer/source-panel-template.html b/tools/system-analyzer/source-panel-template.html
new file mode 100644
index 0000000000..d3b77b27bc
--- /dev/null
+++ b/tools/system-analyzer/source-panel-template.html
@@ -0,0 +1,34 @@
+
+
+
+
diff --git a/tools/system-analyzer/source-panel.mjs b/tools/system-analyzer/source-panel.mjs
new file mode 100644
index 0000000000..be0fa266eb
--- /dev/null
+++ b/tools/system-analyzer/source-panel.mjs
@@ -0,0 +1,72 @@
+// 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, defineCustomElement } from "./helper.mjs";
+
+defineCustomElement(
+ "source-panel",
+ (templateText) =>
+ class SourcePanel extends V8CustomElement {
+ #selectedSourcePositions;
+ constructor() {
+ super(templateText);
+ }
+ get script() {
+ return this.$('#script');
+ }
+ get scriptNode() {
+ return this.$('.scriptNode');
+ }
+ set script(script) {
+ this.renderSourcePanel(script);
+ }
+ set selectedSourcePositions(sourcePositions) {
+ this.#selectedSourcePositions = sourcePositions;
+ this.renderSourcePanelSelectedHighlight();
+ }
+ get selectedSourcePositions() {
+ return this.#selectedSourcePositions;
+ }
+ highlightSourcePosition(line, col, script) {
+ //TODO(zcankara) change setting source to support multiple files
+ this.script = script;
+ let codeNodes = this.scriptNode.children;
+ for (let index = 1; index <= codeNodes.length; index++) {
+ if (index != line) continue;
+ let lineText = codeNodes[index - 1].innerHTML;
+ for (let char = 1; char <= lineText.length; char++) {
+ if (char != col) continue;
+ let target = char - 1;
+ codeNodes[line - 1].innerHTML = lineText.slice(0, target) +
+ "
" +
+ lineText.slice(target, lineText.length);
+ }
+ }
+ }
+ createScriptNode() {
+ let scriptNode = document.createElement("pre");
+ scriptNode.classList.add('scriptNode');
+ return scriptNode;
+ }
+ renderSourcePanel(source) {
+ let scriptNode = this.createScriptNode();
+ let sourceLines = source.split("\n");
+ for (let line = 1; line <= sourceLines.length; line++) {
+ let codeNode = document.createElement("code");
+ codeNode.classList.add("line" + line);
+ codeNode.innerHTML = sourceLines[line - 1] + "\n";
+ scriptNode.appendChild(codeNode);
+ }
+ let oldScriptNode = this.script.childNodes[1];
+ this.script.replaceChild(scriptNode, oldScriptNode);
+ }
+ renderSourcePanelSelectedHighlight() {
+ for (const sourcePosition of this.selectedSourcePositions) {
+ let line = sourcePosition.line;
+ let col = sourcePosition.col;
+ let script = sourcePosition.script;
+ this.highlightSourcePosition(line, col, script);
+ }
+ }
+ }
+);