Commit ff4833f1 authored by Zeynep Cankara's avatar Zeynep Cankara Committed by Commit Bot

[tools][system-analyzer] Create timeline track component

This CL creates a timeline track component to
make the timeline view extensible as different
data sources added. The timeline track component will
take data source and display it with respect to time
axis of timeline overview.

Bug: v8:10644, v8:10735

Change-Id: I1c88dd2dc967be68e6235e517dcf8554a891eee4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2302053
Commit-Queue: Zeynep Cankara <zcankara@google.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69102}
parent 38538209
...@@ -2,80 +2,88 @@ ...@@ -2,80 +2,88 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import {$} from './helper.mjs'; import { V8Map } from './map-processor.mjs';
class State { class State {
#mapTimeline; #mapPanel;
#icTimeline; #timelinePanel;
#timeline; #mapTrack;
#transitions; #icTrack;
constructor(mapPanelId, timelinePanelId) { #map;
this.mapPanel_ = $(mapPanelId); #ic;
this.timelinePanel_ = $(timelinePanelId); #navigation;
this._navigation = new Navigation(this); constructor(mapPanel, timelinePanel, mapTrack, icTrack) {
this.timelinePanel_.addEventListener( this.#mapPanel = mapPanel;
this.#timelinePanel = timelinePanel;
this.#mapTrack = mapTrack;
this.#icTrack = icTrack;
this.#navigation = new Navigation(this);
this.#timelinePanel.addEventListener(
'mapchange', e => this.handleMapChange(e)); 'mapchange', e => this.handleMapChange(e));
this.timelinePanel_.addEventListener( this.#timelinePanel.addEventListener(
'showmaps', e => this.handleShowMaps(e)); 'showmaps', e => this.handleShowMaps(e));
this.mapPanel_.addEventListener( this.#mapPanel.addEventListener(
'statemapchange', e => this.handleStateMapChange(e)); 'mapchange', e => this.handleMapChange(e));
this.mapPanel_.addEventListener( this.#mapPanel.addEventListener(
'selectmapdblclick', e => this.handleDblClickSelectMap(e)); 'selectmapdblclick', e => this.handleDblClickSelectMap(e));
this.mapPanel_.addEventListener( this.#mapPanel.addEventListener(
'sourcepositionsclick', e => this.handleClickSourcePositions(e)); 'sourcepositionsclick', e => this.handleClickSourcePositions(e));
} }
get chunks(){
//TODO(zcankara) for timeline dependency
return this.#mapTrack.chunks;
}
handleMapChange(e){ handleMapChange(e){
if (!(e.detail instanceof V8Map)) return;
this.map = e.detail; this.map = e.detail;
} }
handleShowMaps(e){ handleStateMapChange(e){
this.mapPanel_.mapEntries = e.detail.filter(); this.map = e.detail;
}
get icTimeline() {
return this.#icTimeline;
} }
set icTimeline(value) { handleShowMaps(e){
this.#icTimeline = value; if (!(e.detail instanceof V8Map)) return;
this.#mapPanel.mapEntries = e.detail.filter();
} }
set transitions(value) { set transitions(value) {
this.mapPanel_.transitions = value; this.#mapPanel.transitions = value;
}
get timeline() {
return this.#timeline;
} }
set timeline(value) { set mapTimeline(value) {
this.#timeline = value; this.#mapPanel.timeline = value;
this.timelinePanel.timelineEntries = value;
this.timelinePanel.updateTimeline(this.map);
this.mapPanel_.timeline = this.timeline;
}
get chunks() {
return this.timelinePanel.chunks;
} }
get nofChunks() { get nofChunks() {
return this.timelinePanel.nofChunks; return this.timelinePanel.nofChunks;
} }
set nofChunks(count) { set nofChunks(count) {
this.timelinePanel.nofChunks = count; this.timelinePanel.nofChunks = count;
this.timelinePanel.updateTimeline(this.map);
} }
get mapPanel() { get mapPanel() {
return this.mapPanel_; return this.#mapPanel;
} }
get timelinePanel() { get timelinePanel() {
return this.timelinePanel_; return this.#timelinePanel;
} }
get navigation() { get navigation() {
return this._navigation return this.#navigation
} }
get map() { get map() {
return this.timelinePanel.map; return this.#map;
} }
set map(value) { set map(value) {
if(!value) return; if(!value) return;
this.timelinePanel.map = value; this.#map = value;
this._navigation.updateUrl(); this.#mapTrack.selectedEntry = value;
this.#navigation.updateUrl();
this.mapPanel.map = value; this.mapPanel.map = value;
} }
get ic() {
return this.#ic;
}
set ic(value) {
if(!value) return;
this.#ic = value;
}
get entries() { get entries() {
if (!this.map) return {}; if (!this.map) return {};
return { return {
...@@ -92,11 +100,6 @@ class State { ...@@ -92,11 +100,6 @@ class State {
//TODO(zcankara) Handle double clicked map //TODO(zcankara) Handle double clicked map
console.log("double clicked map: ", e.detail); console.log("double clicked map: ", e.detail);
} }
handleStateMapChange(e){
this.map = e.detail;
}
} }
class Navigation { class Navigation {
...@@ -174,5 +177,4 @@ class Navigation { ...@@ -174,5 +177,4 @@ class Navigation {
} }
} }
export { State };
export { State };
\ No newline at end of file
...@@ -6,6 +6,7 @@ class Event { ...@@ -6,6 +6,7 @@ class Event {
#time; #time;
#type; #type;
constructor(type, time) { constructor(type, time) {
//TODO(zcankara) remove type and add empty getters to override
this.#time = time; this.#time = time;
this.#type = type; this.#type = type;
} }
......
...@@ -118,11 +118,22 @@ function transitionTypeToColor(type) { ...@@ -118,11 +118,22 @@ function transitionTypeToColor(type) {
return CSSColor.primaryColor; return CSSColor.primaryColor;
case 'ReplaceDescriptors': case 'ReplaceDescriptors':
return CSSColor.red; return CSSColor.red;
case 'LoadGlobalIC':
return CSSColor.green;
case 'StoreInArrayLiteralIC':
return CSSColor.violet;
case 'StoreIC':
return CSSColor.orange;
case 'KeyedLoadIC':
return CSSColor.red;
case 'KeyedStoreIC':
return CSSColor.primaryColor;
} }
return CSSColor.primaryColor; return CSSColor.primaryColor;
} }
function div(classes) { function div(classes) {
let node = document.createElement('div'); let node = document.createElement('div');
if (classes !== void 0) { if (classes !== void 0) {
......
...@@ -261,4 +261,4 @@ class Entry extends Event { ...@@ -261,4 +261,4 @@ class Entry extends Event {
} }
} }
export { CustomIcProcessor as default }; export { CustomIcProcessor as default };
\ No newline at end of file
...@@ -93,8 +93,8 @@ found in the LICENSE file. --> ...@@ -93,8 +93,8 @@ found in the LICENSE file. -->
<script type="module" > <script type="module" >
import {App} from './index.mjs'; import {App} from './index.mjs';
globalThis.app = new App("#log-file-reader", "#map-panel", globalThis.app = new App("#log-file-reader", "#map-panel", "#timeline-panel",
"#timeline-panel", "#ic-panel"); "#ic-panel", "#map-track", "#ic-track");
</script> </script>
</head> </head>
<body> <body>
...@@ -105,7 +105,10 @@ globalThis.app = new App("#log-file-reader", "#map-panel", ...@@ -105,7 +105,10 @@ globalThis.app = new App("#log-file-reader", "#map-panel",
<br></br> <br></br>
</section> </section>
<div id="container" style="display: none;"> <div id="container" style="display: none;">
<timeline-panel id="timeline-panel"></timeline-panel> <timeline-panel id="timeline-panel">
<timeline-track id="map-track"></timeline-track>
<timeline-track id="ic-track"></timeline-track>
</timeline-panel>
<map-panel id="map-panel" onclick="app.handleMapAddressSearch(event)" onchange="app.handleShowMaps(event)"></map-panel> <map-panel id="map-panel" onclick="app.handleMapAddressSearch(event)" onchange="app.handleShowMaps(event)"></map-panel>
<ic-panel id="ic-panel" onchange="app.handleSelectIc(event)"></ic-panel> <ic-panel id="ic-panel" onchange="app.handleSelectIc(event)"></ic-panel>
</div> </div>
......
...@@ -11,23 +11,31 @@ import './map-panel.mjs'; ...@@ -11,23 +11,31 @@ import './map-panel.mjs';
import './log-file-reader.mjs'; import './log-file-reader.mjs';
class App { class App {
#timeSelection = {start: 0, end: Infinity}; #timeSelection = {start: 0, end: Infinity};
constructor(fileReaderId, mapPanelId, timelinePanelId, icPanelId) { #mapPanel;
this.mapPanelId_ = mapPanelId; #timelinePanel;
this.timelinePanelId_ = timelinePanelId; #icPanel;
this.icPanelId_ = icPanelId; #mapTrack;
this.icPanel_ = this.$(icPanelId); #icTrack;
this.fileLoaded = false; #logFileReader;
this.logFileReader_ = this.$(fileReaderId); constructor(fileReaderId, mapPanelId, timelinePanelId,
this.logFileReader_.addEventListener('fileuploadstart', icPanelId, mapTrackId, icTrackId) {
this.#logFileReader = this.$(fileReaderId);
this.#mapPanel = this.$(mapPanelId);
this.#timelinePanel = this.$(timelinePanelId);
this.#icPanel = this.$(icPanelId);
this.#mapTrack = this.$(mapTrackId);
this.#icTrack = this.$(icTrackId);
this.#logFileReader.addEventListener('fileuploadstart',
e => this.handleFileUpload(e)); e => this.handleFileUpload(e));
this.logFileReader_.addEventListener('fileuploadend', this.#logFileReader.addEventListener('fileuploadend',
e => this.handleDataUpload(e)); e => this.handleDataUpload(e));
document.addEventListener('keydown', e => this.handleKeyDown(e)); document.addEventListener('keydown', e => this.handleKeyDown(e));
this.icPanel_.addEventListener( this.#icPanel.addEventListener(
'ictimefilter', e => this.handleICTimeFilter(e)); 'ictimefilter', e => this.handleICTimeFilter(e));
this.icPanel_.addEventListener( this.#icPanel.addEventListener(
'mapclick', e => this.handleMapClick(e)); 'mapclick', e => this.handleMapClick(e));
this.icPanel_.addEventListener( this.#icPanel.addEventListener(
'filepositionclick', e => this.handleFilePositionClick(e)); 'filepositionclick', e => this.handleFilePositionClick(e));
this.toggleSwitch = this.$('.theme-switch input[type="checkbox"]'); this.toggleSwitch = this.$('.theme-switch input[type="checkbox"]');
this.toggleSwitch.addEventListener('change', e => this.switchTheme(e)); this.toggleSwitch.addEventListener('change', e => this.switchTheme(e));
...@@ -48,9 +56,9 @@ class App { ...@@ -48,9 +56,9 @@ class App {
handleICTimeFilter(event) { handleICTimeFilter(event) {
this.#timeSelection.start = event.detail.startTime; this.#timeSelection.start = event.detail.startTime;
this.#timeSelection.end = event.detail.endTime; this.#timeSelection.end = event.detail.endTime;
document.state.icTimeline.selectTimeRange(this.#timeSelection.start, this.#icTrack.data.selectTimeRange(this.#timeSelection.start,
this.#timeSelection.end); this.#timeSelection.end);
this.icPanel_.filteredEntries = document.state.icTimeline.selection; this.#icPanel.filteredEntries = this.#icTrack.data.selection;
} }
...@@ -102,13 +110,13 @@ class App { ...@@ -102,13 +110,13 @@ class App {
reader.onload = (evt) => { reader.onload = (evt) => {
let icProcessor = new CustomIcProcessor(); let icProcessor = new CustomIcProcessor();
//TODO(zcankara) Assign timeline directly to the ic panel //TODO(zcankara) Assign timeline directly to the ic panel
//TODO(zcankara) Exe: this.icPanel_.timeline = document.state.icTimeline //TODO(zcankara) Exe: this.#icPanel.timeline = document.state.icTimeline
document.state.icTimeline = icProcessor.processString(fileData.chunk); this.#icTrack.data = icProcessor.processString(fileData.chunk);
this.icPanel_.filteredEntries = document.state.icTimeline.all; this.#icPanel.filteredEntries = this.#icTrack.data.all;
this.icPanel_.count.innerHTML = document.state.icTimeline.all.length; this.#icPanel.count.innerHTML = this.#icTrack.data.all.length;
} }
reader.readAsText(fileData.file); reader.readAsText(fileData.file);
this.icPanel_.initGroupKeySelect(); this.#icPanel.initGroupKeySelect();
} }
// call when a new file uploaded // call when a new file uploaded
...@@ -117,12 +125,14 @@ class App { ...@@ -117,12 +125,14 @@ class App {
this.$('#container').style.display = 'block'; this.$('#container').style.display = 'block';
// instantiate the app logic // instantiate the app logic
let fileData = e.detail; let fileData = e.detail;
document.state = new State(this.mapPanelId_, this.timelinePanelId_); document.state = new State(this.#mapPanel, this.#timelinePanel,
this.#mapTrack, this.#icTrack);
try { try {
const timeline = this.handleLoadTextMapProcessor(fileData.chunk); const timeline = this.handleLoadTextMapProcessor(fileData.chunk);
// Transitions must be set before timeline for stats panel. // Transitions must be set before timeline for stats panel.
document.state.transitions= timeline.transitions; document.state.transitions= timeline.transitions;
document.state.timeline = timeline; this.#mapTrack.data = timeline;
document.state.mapTimeline = timeline;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
......
...@@ -45,7 +45,7 @@ defineCustomElement('./map-panel/map-transitions', (templateText) => ...@@ -45,7 +45,7 @@ defineCustomElement('./map-panel/map-transitions', (templateText) =>
this.dispatchEvent(new CustomEvent( this.dispatchEvent(new CustomEvent(
'mapdetailsupdate', {bubbles: true, composed: true, detail: map})); 'mapdetailsupdate', {bubbles: true, composed: true, detail: map}));
this.dispatchEvent(new CustomEvent( this.dispatchEvent(new CustomEvent(
'statemapchange', {bubbles: true, composed: true, detail: map})); 'mapchange', {bubbles: true, composed: true, detail: map}));
} }
dblClickSelectMap(map) { dblClickSelectMap(map) {
......
...@@ -247,11 +247,12 @@ class V8Map extends Event { ...@@ -247,11 +247,12 @@ class V8Map extends Event {
leftId= 0; leftId= 0;
rightId = 0; rightId = 0;
filePosition = ''; filePosition = '';
id = -1;
constructor(id, time = -1) { constructor(id, time) {
if (time <= 0) throw new Error('Invalid time'); if (!time) throw new Error('Invalid time');
super(id, time); super(id, time);
V8Map.set(id, this); V8Map.set(id, this);
this.id = id;
} }
finalizeRootMap(id) { finalizeRootMap(id) {
...@@ -333,7 +334,7 @@ class V8Map extends Event { ...@@ -333,7 +334,7 @@ class V8Map extends Event {
return transitions; return transitions;
} }
getType() { get type() {
return this.edge === void 0 ? 'new' : this.edge.type; return this.edge === void 0 ? 'new' : this.edge.type;
} }
...@@ -516,4 +517,4 @@ class ArgumentsProcessor extends BaseArgumentsProcessor { ...@@ -516,4 +517,4 @@ class ArgumentsProcessor extends BaseArgumentsProcessor {
} }
} }
export { MapProcessor, V8Map, kChunkWidth, kChunkHeight}; export { MapProcessor, V8Map, kChunkWidth, kChunkHeight};
\ No newline at end of file
...@@ -4,55 +4,6 @@ found in the LICENSE file. --> ...@@ -4,55 +4,6 @@ found in the LICENSE file. -->
<style> <style>
@import "./index.css"; @import "./index.css";
#timeline {
position: relative;
height: 300px;
overflow-y: hidden;
overflow-x: scroll;
user-select: none;
background-color: var(--timeline-background-color);
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
}
#timelineLabel {
transform: rotate(90deg);
transform-origin: left bottom 0;
position: absolute;
left: 0;
width: 250px;
text-align: center;
font-size: 10px;
opacity: 0.5;
}
#timelineChunks {
height: 250px;
position: absolute;
margin-right: 100px;
}
#timelineCanvas {
height: 250px;
position: relative;
overflow: visible;
pointer-events: none;
}
.chunk {
width: 6px;
border: 0px var(--timeline-background-color) solid;
border-width: 0 2px 0 2px;
position: absolute;
background-size: 100% 100%;
image-rendering: pixelated;
bottom: 0px;
}
.timestamp {
height: 250px;
width: 100px;
border-left: 1px var(--surface-color) dashed;
padding-left: 4px;
position: absolute;
pointer-events: none;
font-size: 10px;
opacity: 0.5;
}
#timelineOverview { #timelineOverview {
width: 100%; width: 100%;
height: 50px; height: 50px;
...@@ -89,10 +40,8 @@ found in the LICENSE file. --> ...@@ -89,10 +40,8 @@ found in the LICENSE file. -->
<div class="panel"> <div class="panel">
<h2>Timeline Panel</h2> <h2>Timeline Panel</h2>
<h3>Timeline</h3> <h3>Timeline</h3>
<div id="timeline"> <div>
<div id="timelineLabel">Frequency</div> <slot></slot>
<div id="timelineChunks"></div>
<canvas id="timelineCanvas"></canvas>
</div> </div>
<div id="timelineOverview"> <div id="timelineOverview">
<div id="timelineOverviewIndicator"> <div id="timelineOverviewIndicator">
......
This diff is collapsed.
<!-- 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. -->
<head>
<link href="./index.css" rel="stylesheet">
</head>
<style>
#timeline {
position: relative;
height: 300px;
overflow-y: hidden;
overflow-x: scroll;
user-select: none;
background-color: var(--timeline-background-color);
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
}
#timelineLabel {
transform: rotate(90deg);
transform-origin: left bottom 0;
position: absolute;
left: 0;
width: 250px;
text-align: center;
font-size: 10px;
opacity: 0.5;
}
#timelineChunks {
height: 250px;
position: absolute;
margin-right: 100px;
}
#timelineCanvas {
height: 250px;
position: relative;
overflow: visible;
pointer-events: none;
}
.chunk {
width: 6px;
border: 0px var(--timeline-background-color) solid;
border-width: 0 2px 0 2px;
position: absolute;
background-size: 100% 100%;
image-rendering: pixelated;
bottom: 0px;
}
.timestamp {
height: 250px;
width: 100px;
border-left: 1px var(--surface-color) dashed;
padding-left: 4px;
position: absolute;
pointer-events: none;
font-size: 10px;
opacity: 0.5;
}
#timelineLegend {
position: relative;
float: right;
text-align: center;
}
.timeline {
background-color: var(--timeline-background-color);
}
</style>
<div class="timeline">
<div id="timelineLegend">
<p>Category</p>
<dl id="timelineLegendContent" style="float:right; padding:20px">
</dl>
</div>
<div id="timeline">
<div id="timelineLabel">Frequency</div>
<div id="timelineChunks"></div>
<canvas id="timelineCanvas"></canvas>
</div>
</div>
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment