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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {$} from './helper.mjs';
import { V8Map } from './map-processor.mjs';
class State {
#mapTimeline;
#icTimeline;
#timeline;
#transitions;
constructor(mapPanelId, timelinePanelId) {
this.mapPanel_ = $(mapPanelId);
this.timelinePanel_ = $(timelinePanelId);
this._navigation = new Navigation(this);
this.timelinePanel_.addEventListener(
#mapPanel;
#timelinePanel;
#mapTrack;
#icTrack;
#map;
#ic;
#navigation;
constructor(mapPanel, timelinePanel, mapTrack, icTrack) {
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));
this.timelinePanel_.addEventListener(
this.#timelinePanel.addEventListener(
'showmaps', e => this.handleShowMaps(e));
this.mapPanel_.addEventListener(
'statemapchange', e => this.handleStateMapChange(e));
this.mapPanel_.addEventListener(
this.#mapPanel.addEventListener(
'mapchange', e => this.handleMapChange(e));
this.#mapPanel.addEventListener(
'selectmapdblclick', e => this.handleDblClickSelectMap(e));
this.mapPanel_.addEventListener(
this.#mapPanel.addEventListener(
'sourcepositionsclick', e => this.handleClickSourcePositions(e));
}
get chunks(){
//TODO(zcankara) for timeline dependency
return this.#mapTrack.chunks;
}
handleMapChange(e){
if (!(e.detail instanceof V8Map)) return;
this.map = e.detail;
}
handleShowMaps(e){
this.mapPanel_.mapEntries = e.detail.filter();
}
get icTimeline() {
return this.#icTimeline;
handleStateMapChange(e){
this.map = e.detail;
}
set icTimeline(value) {
this.#icTimeline = value;
handleShowMaps(e){
if (!(e.detail instanceof V8Map)) return;
this.#mapPanel.mapEntries = e.detail.filter();
}
set transitions(value) {
this.mapPanel_.transitions = value;
}
get timeline() {
return this.#timeline;
this.#mapPanel.transitions = value;
}
set timeline(value) {
this.#timeline = value;
this.timelinePanel.timelineEntries = value;
this.timelinePanel.updateTimeline(this.map);
this.mapPanel_.timeline = this.timeline;
}
get chunks() {
return this.timelinePanel.chunks;
set mapTimeline(value) {
this.#mapPanel.timeline = value;
}
get nofChunks() {
return this.timelinePanel.nofChunks;
}
set nofChunks(count) {
this.timelinePanel.nofChunks = count;
this.timelinePanel.updateTimeline(this.map);
}
get mapPanel() {
return this.mapPanel_;
return this.#mapPanel;
}
get timelinePanel() {
return this.timelinePanel_;
return this.#timelinePanel;
}
get navigation() {
return this._navigation
return this.#navigation
}
get map() {
return this.timelinePanel.map;
return this.#map;
}
set map(value) {
if(!value) return;
this.timelinePanel.map = value;
this._navigation.updateUrl();
this.#map = value;
this.#mapTrack.selectedEntry = value;
this.#navigation.updateUrl();
this.mapPanel.map = value;
}
get ic() {
return this.#ic;
}
set ic(value) {
if(!value) return;
this.#ic = value;
}
get entries() {
if (!this.map) return {};
return {
......@@ -92,11 +100,6 @@ class State {
//TODO(zcankara) Handle double clicked map
console.log("double clicked map: ", e.detail);
}
handleStateMapChange(e){
this.map = e.detail;
}
}
class Navigation {
......@@ -174,5 +177,4 @@ class Navigation {
}
}
export { State };
\ No newline at end of file
export { State };
......@@ -6,6 +6,7 @@ class Event {
#time;
#type;
constructor(type, time) {
//TODO(zcankara) remove type and add empty getters to override
this.#time = time;
this.#type = type;
}
......
......@@ -118,11 +118,22 @@ function transitionTypeToColor(type) {
return CSSColor.primaryColor;
case 'ReplaceDescriptors':
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;
}
function div(classes) {
let node = document.createElement('div');
if (classes !== void 0) {
......
......@@ -261,4 +261,4 @@ class Entry extends Event {
}
}
export { CustomIcProcessor as default };
\ No newline at end of file
export { CustomIcProcessor as default };
......@@ -93,8 +93,8 @@ found in the LICENSE file. -->
<script type="module" >
import {App} from './index.mjs';
globalThis.app = new App("#log-file-reader", "#map-panel",
"#timeline-panel", "#ic-panel");
globalThis.app = new App("#log-file-reader", "#map-panel", "#timeline-panel",
"#ic-panel", "#map-track", "#ic-track");
</script>
</head>
<body>
......@@ -105,7 +105,10 @@ globalThis.app = new App("#log-file-reader", "#map-panel",
<br></br>
</section>
<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>
<ic-panel id="ic-panel" onchange="app.handleSelectIc(event)"></ic-panel>
</div>
......
......@@ -11,23 +11,31 @@ import './map-panel.mjs';
import './log-file-reader.mjs';
class App {
#timeSelection = {start: 0, end: Infinity};
constructor(fileReaderId, mapPanelId, timelinePanelId, icPanelId) {
this.mapPanelId_ = mapPanelId;
this.timelinePanelId_ = timelinePanelId;
this.icPanelId_ = icPanelId;
this.icPanel_ = this.$(icPanelId);
this.fileLoaded = false;
this.logFileReader_ = this.$(fileReaderId);
this.logFileReader_.addEventListener('fileuploadstart',
#mapPanel;
#timelinePanel;
#icPanel;
#mapTrack;
#icTrack;
#logFileReader;
constructor(fileReaderId, mapPanelId, timelinePanelId,
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));
this.logFileReader_.addEventListener('fileuploadend',
this.#logFileReader.addEventListener('fileuploadend',
e => this.handleDataUpload(e));
document.addEventListener('keydown', e => this.handleKeyDown(e));
this.icPanel_.addEventListener(
this.#icPanel.addEventListener(
'ictimefilter', e => this.handleICTimeFilter(e));
this.icPanel_.addEventListener(
this.#icPanel.addEventListener(
'mapclick', e => this.handleMapClick(e));
this.icPanel_.addEventListener(
this.#icPanel.addEventListener(
'filepositionclick', e => this.handleFilePositionClick(e));
this.toggleSwitch = this.$('.theme-switch input[type="checkbox"]');
this.toggleSwitch.addEventListener('change', e => this.switchTheme(e));
......@@ -48,9 +56,9 @@ class App {
handleICTimeFilter(event) {
this.#timeSelection.start = event.detail.startTime;
this.#timeSelection.end = event.detail.endTime;
document.state.icTimeline.selectTimeRange(this.#timeSelection.start,
this.#icTrack.data.selectTimeRange(this.#timeSelection.start,
this.#timeSelection.end);
this.icPanel_.filteredEntries = document.state.icTimeline.selection;
this.#icPanel.filteredEntries = this.#icTrack.data.selection;
}
......@@ -102,13 +110,13 @@ class App {
reader.onload = (evt) => {
let icProcessor = new CustomIcProcessor();
//TODO(zcankara) Assign timeline directly to the ic panel
//TODO(zcankara) Exe: this.icPanel_.timeline = document.state.icTimeline
document.state.icTimeline = icProcessor.processString(fileData.chunk);
this.icPanel_.filteredEntries = document.state.icTimeline.all;
this.icPanel_.count.innerHTML = document.state.icTimeline.all.length;
//TODO(zcankara) Exe: this.#icPanel.timeline = document.state.icTimeline
this.#icTrack.data = icProcessor.processString(fileData.chunk);
this.#icPanel.filteredEntries = this.#icTrack.data.all;
this.#icPanel.count.innerHTML = this.#icTrack.data.all.length;
}
reader.readAsText(fileData.file);
this.icPanel_.initGroupKeySelect();
this.#icPanel.initGroupKeySelect();
}
// call when a new file uploaded
......@@ -117,12 +125,14 @@ class App {
this.$('#container').style.display = 'block';
// instantiate the app logic
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 {
const timeline = this.handleLoadTextMapProcessor(fileData.chunk);
// Transitions must be set before timeline for stats panel.
document.state.transitions= timeline.transitions;
document.state.timeline = timeline;
this.#mapTrack.data = timeline;
document.state.mapTimeline = timeline;
} catch (error) {
console.log(error);
}
......
......@@ -45,7 +45,7 @@ defineCustomElement('./map-panel/map-transitions', (templateText) =>
this.dispatchEvent(new CustomEvent(
'mapdetailsupdate', {bubbles: true, composed: true, detail: map}));
this.dispatchEvent(new CustomEvent(
'statemapchange', {bubbles: true, composed: true, detail: map}));
'mapchange', {bubbles: true, composed: true, detail: map}));
}
dblClickSelectMap(map) {
......
......@@ -247,11 +247,12 @@ class V8Map extends Event {
leftId= 0;
rightId = 0;
filePosition = '';
constructor(id, time = -1) {
if (time <= 0) throw new Error('Invalid time');
id = -1;
constructor(id, time) {
if (!time) throw new Error('Invalid time');
super(id, time);
V8Map.set(id, this);
this.id = id;
}
finalizeRootMap(id) {
......@@ -333,7 +334,7 @@ class V8Map extends Event {
return transitions;
}
getType() {
get type() {
return this.edge === void 0 ? 'new' : this.edge.type;
}
......@@ -516,4 +517,4 @@ class ArgumentsProcessor extends BaseArgumentsProcessor {
}
}
export { MapProcessor, V8Map, kChunkWidth, kChunkHeight};
\ No newline at end of file
export { MapProcessor, V8Map, kChunkWidth, kChunkHeight};
......@@ -4,55 +4,6 @@ found in the LICENSE file. -->
<style>
@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 {
width: 100%;
height: 50px;
......@@ -89,10 +40,8 @@ found in the LICENSE file. -->
<div class="panel">
<h2>Timeline Panel</h2>
<h3>Timeline</h3>
<div id="timeline">
<div id="timelineLabel">Frequency</div>
<div id="timelineChunks"></div>
<canvas id="timelineCanvas"></canvas>
<div>
<slot></slot>
</div>
<div id="timelineOverview">
<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