Commit 9c8ebcbb authored by Zeynep Cankara's avatar Zeynep Cankara Committed by Commit Bot

[tools][system-analyzer] Timeline-track filter by time event

This CL adds the functionality to filter log events
falling into the time range specified by the user via
mouse events on timeline tracks. The log event selections
on panels updated based on the selected time range.

Bug: v8:10644

Change-Id: Iaf53896fd5c43cefea6d4c40bab5fcb136494b5f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2351670
Commit-Queue: Zeynep Cankara <zcankara@google.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69375}
parent 833662c7
......@@ -3,48 +3,50 @@
// found in the LICENSE file.
class State {
#timeSelection = {start: 0, end: Infinity};
#timeSelection = { start: 0, end: Infinity };
#map;
#ic;
#selectedMapLogEvents;
#selectedIcLogEvents;
#nofChunks;
#chunks;
#icTimeline;
#mapTimeline;
#minStartTime = Number.POSITIVE_INFINITY;
#maxEndTime = Number.NEGATIVE_INFINITY;
get minStartTime(){
get minStartTime() {
return this.#minStartTime;
}
get maxEndTime(){
get maxEndTime() {
return this.#maxEndTime;
}
#updateTimeRange(timeline){
#updateTimeRange(timeline) {
this.#minStartTime = Math.min(this.#minStartTime, timeline.startTime);
this.#maxEndTime = Math.max(this.#maxEndTime, timeline.endTime);
}
get mapTimeline(){
get mapTimeline() {
return this.#mapTimeline;
}
set mapTimeline(timeline){
set mapTimeline(timeline) {
this.#updateTimeRange(timeline);
timeline.startTime = this.#minStartTime;
timeline.endTime = this.#maxEndTime;
this.#mapTimeline = timeline;
}
set icTimeline(timeline){
set icTimeline(timeline) {
this.#updateTimeRange(timeline);
timeline.startTime = this.#minStartTime;
timeline.endTime = this.#maxEndTime;
this.#icTimeline = timeline;
}
get icTimeline(){
get icTimeline() {
return this.#icTimeline;
}
set chunks(value){
set chunks(value) {
//TODO(zcankara) split up between maps and ics, and every timeline track
this.#chunks = value;
}
get chunks(){
get chunks() {
//TODO(zcankara) split up between maps and ics, and every timeline track
return this.#chunks;
}
......@@ -60,7 +62,7 @@ class State {
}
set map(value) {
//TODO(zcankara) rename as selectedMapEvents, array of selected events
if(!value) return;
if (!value) return;
this.#map = value;
}
get ic() {
......@@ -69,9 +71,23 @@ class State {
}
set ic(value) {
//TODO(zcankara) rename selectedIcEvents, array of selected events
if(!value) return;
if (!value) return;
this.#ic = value;
}
get selectedMapLogEvents() {
return this.#selectedMapLogEvents;
}
set selectedMapLogEvents(value) {
if (!value) return;
this.#selectedMapLogEvents = value;
}
get selectedIcLogEvents() {
return this.#selectedIcLogEvents;
}
set selectedIcLogEvents(value) {
if (!value) return;
this.#selectedIcLogEvents = value;
}
get timeSelection() {
return this.#timeSelection;
}
......
......@@ -4,9 +4,9 @@
// found in the LICENSE file.
class SelectionEvent extends CustomEvent {
constructor(entries){
super('showentries', {bubbles: true, composed: true});
if(!Array.isArray(entries) || entries.length == 0){
constructor(entries) {
super('showentries', { bubbles: true, composed: true });
if (!Array.isArray(entries) || entries.length == 0) {
throw new Error('No valid entries selected!')
}
this.entries = entries;
......@@ -14,10 +14,19 @@ class SelectionEvent extends CustomEvent {
}
class SelectEvent extends CustomEvent {
constructor(entry){
super('showentrydetail', {bubbles: true, composed: true});
constructor(entry) {
super('showentrydetail', { bubbles: true, composed: true });
this.entry = entry;
}
}
export {SelectionEvent, SelectEvent};
\ No newline at end of file
class SelectTimeEvent extends CustomEvent {
static name = 'timerangeselect';
constructor(start, end) {
super(SelectTimeEvent.name, { bubbles: true, composed: true });
this.start = start;
this.end = end;
}
}
export { SelectionEvent, SelectEvent, SelectTimeEvent };
......@@ -2,10 +2,10 @@
// 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 { Group } from './ic-model.mjs';
import CustomIcProcessor from "./ic-processor.mjs";
import {SelectEvent} from './events.mjs';
import {defineCustomElement, V8CustomElement} from './helper.mjs';
import { SelectEvent, SelectTimeEvent } from './events.mjs';
import { defineCustomElement, V8CustomElement } from './helper.mjs';
defineCustomElement('ic-panel', (templateText) =>
class ICPanel extends V8CustomElement {
......@@ -20,7 +20,7 @@ defineCustomElement('ic-panel', (templateText) =>
'click', e => this.handleICTimeFilter(e));
}
get entries(){
get entries() {
return this.#entries;
}
......@@ -40,16 +40,16 @@ defineCustomElement('ic-panel', (templateText) =>
return this.$('#count');
}
get spanSelectAll(){
get spanSelectAll() {
return this.querySelectorAll("span");
}
set filteredEntries(value){
set filteredEntries(value) {
this.#filteredEntries = value;
this.updateTable();
}
get filteredEntries(){
get filteredEntries() {
return this.#filteredEntries;
}
......@@ -92,11 +92,11 @@ defineCustomElement('ic-panel', (templateText) =>
return node
}
handleMapClick(e){
handleMapClick(e) {
this.dispatchEvent(new SelectEvent(e.target.parentNode.entry));
}
handleFilePositionClick(e){
handleFilePositionClick(e) {
this.dispatchEvent(new SelectEvent(e.target.parentNode.entry.key));
}
......@@ -114,7 +114,7 @@ defineCustomElement('ic-panel', (templateText) =>
tr.addEventListener('click',
e => this.handleFilePositionClick(e));
}
let details = this.td(tr,'<span>&#8505;</a>', 'details');
let details = this.td(tr, '<span>&#8505;</a>', 'details');
//TODO(zcankara) don't keep the whole function context alive
details.onclick = _ => this.toggleDetails(details);
this.td(tr, entry.percentage + "%", 'percentage');
......@@ -190,11 +190,9 @@ defineCustomElement('ic-panel', (templateText) =>
}
handleICTimeFilter(e) {
const startTime = parseInt(this.$('#filter-time-start').value);
const endTime = parseInt(this.$('#filter-time-end').value);
const dataModel = {startTime, endTime};
this.dispatchEvent(new CustomEvent(
'ictimefilter', {bubbles: true, composed: true, detail: dataModel}));
this.dispatchEvent(new SelectTimeEvent(
parseInt(this.$('#filter-time-start').value),
parseInt(this.$('#filter-time-end').value)));
}
});
});
......@@ -6,6 +6,7 @@ import CustomIcProcessor from "./ic-processor.mjs";
import { Entry } from "./ic-processor.mjs";
import { State } from "./app-model.mjs";
import { MapProcessor, V8Map } from "./map-processor.mjs";
import { SelectTimeEvent } from "./events.mjs";
import { $ } from "./helper.mjs";
import "./ic-panel.mjs";
import "./timeline-panel.mjs";
......@@ -38,26 +39,28 @@ class App {
this.handleDataUpload(e)
);
Object.entries(this.#view).forEach(([_, value]) => {
value.addEventListener("showentries", (e) => this.handleShowEntries(e));
value.addEventListener("showentrydetail", (e) =>
this.handleShowEntryDetail(e)
);
value.addEventListener('showentries',
e => this.handleShowEntries(e));
value.addEventListener('showentrydetail',
e => this.handleShowEntryDetail(e));
value.addEventListener(SelectTimeEvent.name,
e => this.handleTimeRangeSelect(e));
});
this.#view.icPanel.addEventListener("ictimefilter", (e) =>
this.handleICTimeFilter(e)
);
}
handleShowEntries(e) {
if (e.entries[0] instanceof V8Map) {
this.#view.mapPanel.mapEntries = e.entries;
this.showMapEntries(e.entries);
}
}
handleTimeRangeSelect(e) {
this.selectTimeRange(e.start, e.end);
}
handleShowEntryDetail(e) {
if (e.entry instanceof V8Map) {
this.selectMapLogEvent(e.entry);
} else if (e.entry instanceof Entry) {
this.selectICLogEvent(e.entry);
} else if (typeof e.entry === "string") {
} else if (typeof e.entry === 'string') {
this.selectSourcePositionEvent(e.entry);
} else {
console.log("undefined");
......@@ -67,6 +70,18 @@ class App {
//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;
this.#state.icTimeline.selectTimeRange(start, end);
this.#state.mapTimeline.selectTimeRange(start, end);
this.#view.mapPanel.selectedMapLogEvents = this.#state.mapTimeline.selection;
this.#view.icPanel.filteredEntries = this.#state.icTimeline.selection;
}
showMapEntries(entries) {
this.#state.selectedMapLogEvents = entries;
this.#view.mapPanel.selectedMapLogEvents = this.#state.selectedMapLogEvents;
}
selectMapLogEvent(entry) {
this.#state.map = entry;
this.#view.mapTrack.selectedEntry = entry;
......@@ -78,15 +93,6 @@ class App {
selectSourcePositionEvent(sourcePositions) {
console.log("source positions: ", sourcePositions);
}
handleICTimeFilter(event) {
this.#state.timeSelection.start = event.detail.startTime;
this.#state.timeSelection.end = event.detail.endTime;
this.#view.icTrack.data.selectTimeRange(
this.#state.timeSelection.start,
this.#state.timeSelection.end
);
this.#view.icPanel.filteredEntries = this.#view.icTrack.data.selection;
}
handleFileUpload(e) {
$("#container").className = "initial";
}
......
......@@ -4,9 +4,9 @@
import "./stats-panel.mjs";
import "./map-panel/map-details.mjs";
import "./map-panel/map-transitions.mjs";
import {SelectEvent} from './events.mjs';
import {V8Map} from "./map-processor.mjs";
import {defineCustomElement, V8CustomElement} from './helper.mjs';
import { SelectEvent } from './events.mjs';
import { V8Map } from "./map-processor.mjs";
import { defineCustomElement, V8CustomElement } from './helper.mjs';
defineCustomElement('map-panel', (templateText) =>
class MapPanel extends V8CustomElement {
......@@ -19,7 +19,7 @@ defineCustomElement('map-panel', (templateText) =>
'mapdetailsupdate', e => this.handleUpdateMapDetails(e));
}
handleUpdateMapDetails(e){
handleUpdateMapDetails(e) {
this.mapDetailsPanel.mapDetails = e.detail;
}
......@@ -68,12 +68,12 @@ defineCustomElement('map-panel', (templateText) =>
this.mapTransitionsPanel.map = this.#map;
}
handleSearchBar(e){
handleSearchBar(e) {
let searchBar = this.$('#searchBarInput');
let searchBarInput = searchBar.value;
//access the map from model cache
let selectedMap = V8Map.get(searchBarInput);
if(selectedMap){
if (selectedMap) {
searchBar.className = "success";
} else {
searchBar.className = "failure";
......@@ -81,11 +81,11 @@ defineCustomElement('map-panel', (templateText) =>
this.dispatchEvent(new SelectEvent(selectedMap));
}
set mapEntries(list){
this.mapTransitionsPanel.mapEntries = list;
set selectedMapLogEvents(list) {
this.mapTransitionsPanel.selectedMapLogEvents = list;
}
get mapEntries(){
return this.mapTransitionsPanel.mapEntries;
get selectedMapLogEvents() {
return this.mapTransitionsPanel.selectedMapLogEvents;
}
});
});
// 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';
import {SelectEvent} from '../events.mjs';
import { V8CustomElement, defineCustomElement } from '../helper.mjs';
import { SelectEvent } from '../events.mjs';
defineCustomElement('./map-panel/map-transitions', (templateText) =>
class MapTransitions extends V8CustomElement {
#map;
#mapEntries;
#selectedMapLogEvents;
constructor() {
super(templateText);
this.transitionView.addEventListener(
......@@ -33,7 +33,7 @@ defineCustomElement('./map-panel/map-transitions', (templateText) =>
this.showMap();
}
handleTransitionViewChange(e){
handleTransitionViewChange(e) {
this.tooltip.style.left = e.pageX + "px";
this.tooltip.style.top = e.pageY + "px";
let map = e.target.map;
......@@ -45,7 +45,7 @@ defineCustomElement('./map-panel/map-transitions', (templateText) =>
selectMap(map) {
this.currentMap = map;
this.dispatchEvent(new CustomEvent(
'mapdetailsupdate', {bubbles: true, composed: true, detail: map}));
'mapdetailsupdate', { bubbles: true, composed: true, detail: map }));
this.dispatchEvent(new SelectEvent(map));
}
......@@ -58,26 +58,26 @@ defineCustomElement('./map-panel/map-transitions', (templateText) =>
let selected = this.#map;
if (this.currentMap === this.#map) return;
this.currentMap = this.#map;
this.mapEntries = [this.#map];
this.selectedMapLogEvents = [this.#map];
this.dispatchEvent(new CustomEvent(
'mapdetailsupdate', {bubbles: true, composed: true, detail: selected}));
'mapdetailsupdate', { bubbles: true, composed: true, detail: selected }));
}
showMaps() {
// Timeline dbl click to show map transitions of selected maps
this.transitionView.style.display = 'none';
this.removeAllChildren(this.transitionView);
this.mapEntries.forEach(map => this.addMapAndParentTransitions(map));
this.selectedMapLogEvents.forEach(map => this.addMapAndParentTransitions(map));
this.transitionView.style.display = '';
}
set mapEntries(list){
this.#mapEntries = list;
set selectedMapLogEvents(list) {
this.#selectedMapLogEvents = list;
this.showMaps();
}
get mapEntries(){
return this.#mapEntries;
get selectedMapLogEvents() {
return this.#selectedMapLogEvents;
}
addMapAndParentTransitions(map) {
......@@ -94,7 +94,7 @@ defineCustomElement('./map-panel/map-transitions', (templateText) =>
if (this.selectedMap == map) {
setTimeout(
() => mapNode.scrollIntoView(
{behavior: 'smooth', block: 'nearest', inline: 'nearest'}),
{ behavior: 'smooth', block: 'nearest', inline: 'nearest' }),
1);
}
}
......@@ -182,4 +182,4 @@ defineCustomElement('./map-panel/map-transitions', (templateText) =>
}
}
});
});
......@@ -2,10 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {defineCustomElement, V8CustomElement,
transitionTypeToColor, CSSColor} from '../helper.mjs';
import {kChunkWidth, kChunkHeight} from '../map-processor.mjs';
import {SelectionEvent, SelectEvent} from '../events.mjs';
import {
defineCustomElement, V8CustomElement,
transitionTypeToColor, CSSColor
} from '../helper.mjs';
import { kChunkWidth, kChunkHeight } from '../map-processor.mjs';
import { SelectionEvent, SelectEvent, SelectTimeEvent } from '../events.mjs';
defineCustomElement('./timeline/timeline-track', (templateText) =>
class TimelineTrack extends V8CustomElement {
......@@ -13,8 +15,14 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
#nofChunks = 400;
#chunks;
#selectedEntry;
#timeToPixel;
#timeSelection = { start: 0, end: Infinity };
constructor() {
super(templateText);
this.timeline.addEventListener("mousedown",
e => this.handleTimeRangeSelectionStart(e));
this.timeline.addEventListener("mouseup",
e => this.handleTimeRangeSelectionEnd(e));
this.timeline.addEventListener("scroll",
e => this.handleTimelineScroll(e));
this.backgroundCanvas = document.createElement('canvas');
......@@ -48,36 +56,36 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
return this.#timeline;
}
set nofChunks(count){
set nofChunks(count) {
this.#nofChunks = count;
this.updateChunks();
this.updateTimeline();
}
get nofChunks(){
get nofChunks() {
return this.#nofChunks;
}
updateChunks() {
this.#chunks = this.data.chunks(this.nofChunks);
}
get chunks(){
get chunks() {
return this.#chunks;
}
set selectedEntry(value){
set selectedEntry(value) {
this.#selectedEntry = value;
if(value.edge) this.redraw();
if (value.edge) this.redraw();
}
get selectedEntry(){
get selectedEntry() {
return this.#selectedEntry;
}
set scrollLeft(offset){
set scrollLeft(offset) {
this.timeline.scrollLeft = offset;
}
updateStats(){
updateStats() {
let unique = new Map();
for (const entry of this.data.all) {
if(!unique.has(entry.type)) {
if (!unique.has(entry.type)) {
unique.set(entry.type, [entry]);
} else {
unique.get(entry.type).push(entry);
......@@ -86,7 +94,7 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
this.renderStatsWindow(unique);
}
renderStatsWindow(unique){
renderStatsWindow(unique) {
let timelineLegendContent = this.timelineLegendContent;
this.removeAllChildren(timelineLegendContent);
let fragment = document.createDocumentFragment();
......@@ -107,7 +115,7 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
timelineLegendContent.appendChild(fragment);
}
handleEntryTypeDblClick(e){
handleEntryTypeDblClick(e) {
this.dispatchEvent(new SelectionEvent(e.target.entries));
}
......@@ -115,11 +123,31 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
this.timeline.scrollLeft += offset;
}
handleTimelineScroll(e){
handleTimeRangeSelectionStart(e) {
this.#timeSelection.start = this.positionToTime(e.clientX);
}
handleTimeRangeSelectionEnd(e) {
this.#timeSelection.end = this.positionToTime(e.clientX);
this.dispatchEvent(new SelectTimeEvent(
Math.min(this.#timeSelection.start, this.#timeSelection.end),
Math.max(this.#timeSelection.start, this.#timeSelection.end)));
}
positionToTime(posX) {
let rect = this.timeline.getBoundingClientRect();
let posClickedX = posX - rect.left + this.timeline.scrollLeft;
let selectedTime = posClickedX / this.#timeToPixel;
return selectedTime;
}
handleTimelineScroll(e) {
let horizontal = e.currentTarget.scrollLeft;
this.dispatchEvent(new CustomEvent(
'scrolltrack', {bubbles: true, composed: true,
detail: horizontal}));
'scrolltrack', {
bubbles: true, composed: true,
detail: horizontal
}));
}
asyncSetTimelineChunkBackground(backgroundTodo) {
......@@ -176,11 +204,11 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
let start = this.data.startTime;
let end = this.data.endTime;
let duration = end - start;
const timeToPixel = chunks.length * kChunkWidth / duration;
this.#timeToPixel = chunks.length * kChunkWidth / duration;
let addTimestamp = (time, name) => {
let timeNode = this.div('timestamp');
timeNode.innerText = name;
timeNode.style.left = ((time - start) * timeToPixel) + 'px';
timeNode.style.left = ((time - start) * this.#timeToPixel) + 'px';
chunksNode.appendChild(timeNode);
};
let backgroundTodo = [];
......@@ -264,7 +292,7 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
ctx.fill();
let imageData = canvas.toDataURL('image/webp', 0.2);
this.dispatchEvent(new CustomEvent(
'overviewupdate', {bubbles: true, composed: true, detail: imageData}));
'overviewupdate', { bubbles: true, composed: true, detail: imageData }));
}
redraw() {
......
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