Commit e7357f19 authored by zeynepCankara's avatar zeynepCankara Committed by Commit Bot

[tools] Add Indicium

Indicium is a new tool that integrates all our Map and IC processing
tools into one tool.

This CL does not attempt to cleanly integrate the Map Processor
and IC explorer, but provides an in initial starting point for further
integration work.

Bug: v8:10644
Change-Id: I753c116fd409c8c07613bf15f22e14aa1e8c8a0e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2259935
Commit-Queue: Sathya Gunasekaran  <gsathya@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran  <gsathya@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68605}
parent 83ac3742
// 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.
const KB = 1024;
const MB = KB * KB;
const GB = MB * KB;
const kMillis2Seconds = 1 / 1000;
function formatBytes(bytes) {
const units = ['B', 'KiB', 'MiB', 'GiB'];
const divisor = 1024;
let index = 0;
while (index < units.length && bytes >= divisor) {
index++;
bytes /= divisor;
}
return bytes.toFixed(2) + units[index];
}
function formatSeconds(millis) {
return (millis * kMillis2Seconds).toFixed(2) + 's';
}
function defineCustomElement(name, generator) {
let htmlTemplatePath = name + '-template.html';
fetch(htmlTemplatePath)
.then(stream => stream.text())
.then(
templateText => customElements.define(name, generator(templateText)));
}
// 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.
'use strict';
let entries = [];
export let properties = [
'type',
'category',
'functionName',
'filePosition',
'state',
'key',
'map',
'reason',
'file',
];
// For compatibility with console scripts:
print = console.log;
export class Group {
constructor(property, key, entry) {
this.property = property;
this.key = key;
this.count = 1;
this.entries = [entry];
this.percentage = undefined;
this.groups = undefined;
}
add(entry) {
this.count++;
this.entries.push(entry)
}
createSubGroups() {
this.groups = {};
for (let i = 0; i < properties.length; i++) {
let subProperty = properties[i];
if (this.property == subProperty) continue;
this.groups[subProperty] = Group.groupBy(this.entries, subProperty);
}
}
static groupBy(entries, property) {
let accumulator = Object.create(null);
let length = entries.length;
for (let i = 0; i < length; i++) {
let entry = entries[i];
let key = entry[property];
if (accumulator[key] == undefined) {
accumulator[key] = new Group(property, key, entry)
} else {
let group = accumulator[key];
if (group.entries == undefined) console.log([group, entry]);
group.add(entry)
}
}
let result = [];
for (let key in accumulator) {
let group = accumulator[key];
group.percentage = Math.round(group.count / length * 100 * 100) / 100;
result.push(group);
}
result.sort((a, b) => {return b.count - a.count});
return result;
}
}
<!-- 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. -->
<style>
.ic-panel {
background-color: #355EC2;
padding: 20px 20px 20px 20px ;
margin: auto;
}
#ic-panel {
background-color: #232323;
padding: 10px 10px 10px 10px ;
margin: auto;
overflow-x: scroll;
}
.count {
text-align: right;
width: 5em;
}
.percentage {
text-align: right;
width: 5em;
}
.key {
padding-left: 1em;
}
.drilldown-group-title {
font-weight: bold;
padding: 0.5em 0 0.2em 0;
}
.entry-details {
}
.entry-details TD {
}
.details {
width: 0.1em;
}
.details span {
padding: 0 0.4em 0 0.4em;
background-color: black;
color: white;
border-radius: 25px;
text-align: center;
cursor: -webkit-zoom-in;
}
#legend {
padding-right: 20px;
}
</style>
<div class="ic-panel">
<section id="ic-panel">
<h1>IC Explorer</h1>
<div id="legend">
<div style="float:right; border-style: solid; border-width: 1px; padding:20px">
0 uninitialized<br>
X no feedback<br>
1 monomorphic<br>
^ recompute handler<br>
P polymorphic<br>
N megamorphic<br>
G generic
</div>
</div>
<h2>Data</h2>
<p>Trace Count: <span id="count">0</span></p>
<h2>Result</h2>
<p>
Group-Key:
<select id="group-key"></select>
</p>
Filter number of items
<br></br>
<input type="search" id="filter-input" placeholder="Number of items"></input>
<button id="filterICBtn">Filter</button>
<p>
<table id="table" width="100%">
<tbody id="table-body">
</tbody>
</table>
</p>
</section>
</div>
// 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 {Group, properties} from './ic-model.js';
defineCustomElement('ic-panel', (templateText) =>
class ICPanel extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
this.groupKeySelect.addEventListener(
'change', e => this.updateTable(e));
this.filterICBtnSelect.addEventListener(
'click', e => this.handleICFilter(e));
this._noOfItems = 100;
}
$(id) {
return this.shadowRoot.querySelector(id);
}
querySelectorAll(query) {
return this.shadowRoot.querySelectorAll(query);
}
get groupKeySelect() {
return this.$('#group-key');
}
get filterICBtnSelect() {
return this.$('#filterICBtn');
}
get tableSelect() {
return this.$('#table');
}
get tableBodySelect() {
return this.$('#table-body');
}
get countSelect() {
return this.$('#count');
}
get spanSelectAll(){
return this.querySelectorAll("span");
}
updateTable(event) {
let select = this.groupKeySelect;
let key = select.options[select.selectedIndex].text;
let tableBody = this.tableBodySelect;
this.removeAllChildren(tableBody);
let groups = Group.groupBy(entries, key, true);
this.display(groups, tableBody);
}
escapeHtml(unsafe) {
if (!unsafe) return "";
return unsafe.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
processValue(unsafe) {
if (!unsafe) return "";
if (!unsafe.startsWith("http")) return this.escapeHtml(unsafe);
let a = document.createElement("a");
a.href = unsafe;
a.textContent = unsafe;
return a;
}
removeAllChildren(node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
td(tr, content, className) {
let node = document.createElement("td");
if (typeof content == "object") {
node.appendChild(content);
} else {
node.innerHTML = content;
}
node.className = className;
tr.appendChild(node);
return node
}
set noOfItems(value){
this._noOfItems = value;
}
get noOfItems(){
return this._noOfItems;
}
display(entries, parent) {
let fragment = document.createDocumentFragment();
let max = Math.min(this.noOfItems, entries.length)
for (let i = 0; i < max; i++) {
let entry = entries[i];
let tr = document.createElement("tr");
tr.entry = entry;
let details = this.td(tr,'<span>&#8505;</a>', 'details');
details.onclick = _ => this.toggleDetails(details);
this.td(tr, entry.percentage + "%", 'percentage');
this.td(tr, entry.count, 'count');
this.td(tr, this.processValue(entry.key), 'key');
fragment.appendChild(tr);
}
let omitted = entries.length - max;
if (omitted > 0) {
let tr = document.createElement("tr");
let tdNode = td(tr, 'Omitted ' + omitted + " entries.");
tdNode.colSpan = 4;
fragment.appendChild(tr);
}
parent.appendChild(fragment);
}
displayDrilldown(entry, previousSibling) {
let tr = document.createElement('tr');
tr.className = "entry-details";
tr.style.display = "none";
// indent by one td.
tr.appendChild(document.createElement("td"));
let td = document.createElement("td");
td.colSpan = 3;
for (let key in entry.groups) {
td.appendChild(this.displayDrilldownGroup(entry, key));
}
tr.appendChild(td);
// Append the new TR after previousSibling.
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling)
}
displayDrilldownGroup(entry, key) {
let max = 20;
let group = entry.groups[key];
let div = document.createElement("div")
div.className = 'drilldown-group-title'
div.textContent = key + ' [top ' + max + ' out of ' + group.length + ']';
let table = document.createElement("table");
this.display(group.slice(0, max), table, false)
div.appendChild(table);
return div;
}
toggleDetails(node) {
let tr = node.parentNode;
let entry = tr.entry;
// Create subgroup in-place if the don't exist yet.
if (entry.groups === undefined) {
entry.createSubGroups();
this.displayDrilldown(entry, tr);
}
let details = tr.nextSibling;
let display = details.style.display;
if (display != "none") {
display = "none";
} else {
display = "table-row"
};
details.style.display = display;
}
initGroupKeySelect() {
let select = this.groupKeySelect;
select.options.length = 0;
for (let i in properties) {
let option = document.createElement("option");
option.text = properties[i];
select.add(option);
}
}
//TODO(zc): Function processing the timestamps of ICEvents
// Processes the IC Events which have V8Map's in the map-processor
processICEventTime(){
let ICTimeToEvent = new Map();
// save the occurance time of V8Maps
let eventTimes = []
console.log("Num of stats: " + entries.length);
// fetch V8 Maps from the IC Events
entries.forEach(element => {
let v8Map = V8Map.get("0x" + element.map);
if(!v8Map){
ICTimeToEvent.set(-1, element);
} else {
ICTimeToEvent.set(v8Map.time, element);
eventTimes.push(v8Map.time);
}
});
eventTimes.sort();
// save the IC events which have Map states
let eventsList = [];
for(let i = 0; i < eventTimes.length; i++){
eventsList.push(ICTimeToEvent.get(eventTimes[i]));
}
return eventList;
}
handleICFilter(e){
let noOfItemsInput = parseInt(this.$('#filter-input').value);
this.noOfItems = noOfItemsInput;
this.updateTable(e);
}
});
// 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.
/**
* Parser for dynamic code optimization state.
*/
function parseState(s) {
switch (s) {
case '':
return Profile.CodeState.COMPILED;
case '~':
return Profile.CodeState.OPTIMIZABLE;
case '*':
return Profile.CodeState.OPTIMIZED;
}
throw new Error('unknown code state: ' + s);
}
class IcProcessor extends LogReader {
constructor() {
super();
let propertyICParser = [
parseInt, parseInt, parseInt, parseString, parseString, parseInt,
parseString, parseString, parseString
];
LogReader.call(this, {
'code-creation': {
parsers: [
parseString, parseInt, parseInt, parseInt, parseInt, parseString,
parseVarArgs
],
processor: this.processCodeCreation
},
'code-move':
{parsers: [parseInt, parseInt], processor: this.processCodeMove},
'code-delete': {parsers: [parseInt], processor: this.processCodeDelete},
'sfi-move':
{parsers: [parseInt, parseInt], processor: this.processFunctionMove},
'LoadGlobalIC': {
parsers: propertyICParser,
processor: this.processPropertyIC.bind(this, 'LoadGlobalIC')
},
'StoreGlobalIC': {
parsers: propertyICParser,
processor: this.processPropertyIC.bind(this, 'StoreGlobalIC')
},
'LoadIC': {
parsers: propertyICParser,
processor: this.processPropertyIC.bind(this, 'LoadIC')
},
'StoreIC': {
parsers: propertyICParser,
processor: this.processPropertyIC.bind(this, 'StoreIC')
},
'KeyedLoadIC': {
parsers: propertyICParser,
processor: this.processPropertyIC.bind(this, 'KeyedLoadIC')
},
'KeyedStoreIC': {
parsers: propertyICParser,
processor: this.processPropertyIC.bind(this, 'KeyedStoreIC')
},
'StoreInArrayLiteralIC': {
parsers: propertyICParser,
processor: this.processPropertyIC.bind(this, 'StoreInArrayLiteralIC')
},
});
this.profile_ = new Profile();
this.LoadGlobalIC = 0;
this.StoreGlobalIC = 0;
this.LoadIC = 0;
this.StoreIC = 0;
this.KeyedLoadIC = 0;
this.KeyedStoreIC = 0;
this.StoreInArrayLiteralIC = 0;
}
/**
* @override
*/
printError(str) {
print(str);
}
processString(string) {
let end = string.length;
let current = 0;
let next = 0;
let line;
let i = 0;
let entry;
while (current < end) {
next = string.indexOf('\n', current);
if (next === -1) break;
i++;
line = string.substring(current, next);
current = next + 1;
this.processLogLine(line);
}
}
processLogFile(fileName) {
this.collectEntries = true;
this.lastLogFileName_ = fileName;
let line;
while (line = readline()) {
this.processLogLine(line);
}
print();
print('=====================');
print('LoadGlobal: ' + this.LoadGlobalIC);
print('StoreGlobal: ' + this.StoreGlobalIC);
print('Load: ' + this.LoadIC);
print('Store: ' + this.StoreIC);
print('KeyedLoad: ' + this.KeyedLoadIC);
print('KeyedStore: ' + this.KeyedStoreIC);
print('StoreInArrayLiteral: ' + this.StoreInArrayLiteralIC);
}
addEntry(entry) {
this.entries.push(entry);
}
processCodeCreation(type, kind, timestamp, start, size, name, maybe_func) {
if (maybe_func.length) {
let funcAddr = parseInt(maybe_func[0]);
let state = parseState(maybe_func[1]);
this.profile_.addFuncCode(
type, name, timestamp, start, size, funcAddr, state);
} else {
this.profile_.addCode(type, name, timestamp, start, size);
}
}
processCodeMove(from, to) {
this.profile_.moveCode(from, to);
}
processCodeDelete(start) {
this.profile_.deleteCode(start);
}
processFunctionMove(from, to) {
this.profile_.moveFunc(from, to);
}
formatName(entry) {
if (!entry) return '<unknown>';
let name = entry.func.getName();
let re = /(.*):[0-9]+:[0-9]+$/;
let array = re.exec(name);
if (!array) return name;
return entry.getState() + array[1];
}
// TODO(zc): Process the IC event togather with time
processPropertyIC(
type, pc, line, column, old_state, new_state, map, name, modifier,
slow_reason) {
this[type]++;
let entry = this.profile_.findEntry(pc);
print(
type + ' (' + old_state + '->' + new_state + modifier + ') at ' +
this.formatName(entry) + ':' + line + ':' + column + ' ' + name +
' (map 0x' + map.toString(16) + ')' +
(slow_reason ? ' ' + slow_reason : ''));
}
}
// ================
let entries = [];
let properties = [
'type',
'category',
'functionName',
'filePosition',
'state',
'key',
'map',
'reason',
'file',
];
class CustomIcProcessor extends IcProcessor {
constructor() {
super();
this.entries = [];
}
functionName(pc) {
let entry = this.profile_.findEntry(pc);
return this.formatName(entry);
}
processPropertyIC(
type, pc, line, column, old_state, new_state, map, key, modifier,
slow_reason) {
let fnName = this.functionName(pc);
this.entries.push(new Entry(
type, fnName, line, column, key, old_state, new_state, map,
slow_reason));
}
};
class Entry {
constructor(
type, fn_file, line, column, key, oldState, newState, map, reason,
additional) {
this.type = type;
this.category = 'other';
if (this.type.indexOf('Store') !== -1) {
this.category = 'Store';
} else if (this.type.indexOf('Load') !== -1) {
this.category = 'Load';
}
let parts = fn_file.split(' ');
this.functionName = parts[0];
this.file = parts[1];
let position = line + ':' + column;
this.filePosition = this.file + ':' + position;
this.oldState = oldState;
this.newState = newState;
this.state = this.oldState + ' → ' + this.newState;
this.key = key;
this.map = map.toString(16);
this.reason = reason;
this.additional = additional;
}
parseMapProperties(parts, offset) {
let next = parts[++offset];
if (!next.startsWith('dict')) return offset;
this.propertiesMode = next.substr(5) == '0' ? 'fast' : 'slow';
this.numberOfOwnProperties = parts[++offset].substr(4);
next = parts[++offset];
this.instanceType = next.substr(5, next.length - 6);
return offset;
}
parsePositionAndFile(parts, start) {
// find the position of 'at' in the parts array.
let offset = start;
for (let i = start + 1; i < parts.length; i++) {
offset++;
if (parts[i] == 'at') break;
}
if (parts[offset] !== 'at') return -1;
this.position = parts.slice(start, offset).join(' ');
offset += 1;
this.isNative = parts[offset] == 'native'
offset += this.isNative ? 1 : 0;
this.file = parts[offset];
return offset;
}
}
<!DOCTYPE html>
<!-- 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. -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Indicium</title>
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'>
<!-- <link rel="icon" type="image/png" href="/images/favicon.png"/> -->
<!-- <script type="module" src="index.js"></script> -->
<script src="helper.js"></script>
<script type="module" src="log-file-reader.mjs"></script>
<script type="module" src="map-panel.mjs"></script>
<script type="module" src="timeline-panel.mjs"></script>
<script type="module" src="ic-panel.mjs"></script>
<script src="../splaytree.js"></script>
<script src="../codemap.js"></script>
<script src="../csvparser.js"></script>
<script src="../consarray.js"></script>
<script src="../profile.js"></script>
<script src="../profile_view.js"></script>
<script src="../logreader.js"></script>
<script src="../arguments.js"></script>
<script src="../SourceMap.js"></script>
<script src="./map-processor.js"></script>
<script src="./ic-processor.js"></script>
<script src="./map-model.js"></script>
<style>
body {
font-family: 'Roboto', sans-serif;
color: white;
margin-left: 5%;
margin-right: 5%;
background-color: #041531;
}
.colorbox {
width: 10px;
height: 10px;
border: 1px black solid;
}
#instructions {
padding: 10px 10px 60px 10px ;
margin: auto;
}
#stats {
display: flex;
height: 250px;
background-color: #232323;
padding: 10px 10px 10px 10px ;
margin: auto;
}
.stats-panel {
background-color: #355EC2;
padding: 20px 20px 20px 20px ;
margin: auto;
}
#stats table {
flex: 1;
padding-right: 50px;
max-height: 250px;
display: inline-block;
overflow-y: scroll;
}
#stats table td {
cursor: pointer;
}
#stats .transitionTable {
overflow-y: scroll;
}
#stats .transitionTable tr {
max-width: 200px;
}
#stats .transitionType {
text-align: right;
max-width: 380px;
}
#stats .transitionType tr td:nth-child(2) {
text-align: left;
}
#stats table thead td {
border-bottom: 1px black dotted;
}
/*
.indicium-logo {
width: 380px;
height: 165px;
background-image: url(./images/indicium-logo.png);
background-size: cover;
margin-top: 35px;
}
*/
</style>
<script>
'use strict';
// Event handlers
document.onkeydown = handleKeyDown;
function handleKeyDown(event) {
stateGlobal.navigation = document.state.navigation;
let nav = document.state.navigation;
switch(event.key) {
case "ArrowUp":
event.preventDefault();
if (event.shiftKey) {
nav.selectPrevEdge();
} else {
nav.moveInChunk(-1);
}
return false;
case "ArrowDown":
event.preventDefault();
if (event.shiftKey) {
nav.selectNextEdge();
} else {
nav.moveInChunk(1);
}
return false;
case "ArrowLeft":
nav.moveInChunks(false);
break;
case "ArrowRight":
nav.moveInChunks(true);
break;
case "+":
nav.increaseTimelineResolution();
break;
case "-":
nav.decreaseTimelineResolution();
break;
}
}
// Update application state
function updateDocumentState(){
document.state = stateGlobal.state;
try {
document.state.timeline = stateGlobal.timeline;
} catch (error) {
console.log(error);
console.log("cannot assign timeline to state!");
}
}
// Map event log processing
function handleLoadTextMapProcessor(text) {
let mapProcessor = new MapProcessor();
return mapProcessor.processString(text);
}
// IC event file reading and log processing
function loadFileIC(file) {
let reader = new FileReader();
reader.onload = function(evt) {
let icProcessor = new CustomIcProcessor();
icProcessor.processString(this.result);
entries = icProcessor.entries;
$("ic-panel").countSelect.innerHTML = entries.length;
$("ic-panel").updateTable(entries);
}
reader.readAsText(file);
$("ic-panel").initGroupKeySelect();
}
function $(id) { return document.querySelector(id); }
// holds the state of the application
let stateGlobal = Object.create(null);
// call when a new file uploaded
function globalDataUpload(e) {
stateGlobal.timeline = e.detail;
if(!e.detail) return;
// instantiate the app logic
stateGlobal.fileData = e.detail;
stateGlobal.state = new State();
stateGlobal.timeline = handleLoadTextMapProcessor(stateGlobal.fileData.chunk);
updateDocumentState();
// process the IC explorer
loadFileIC(stateGlobal.fileData.file);
}
function globalSearchBarEvent(e) {
if(!e.detail.isValidMap) return;
document.state.map = e.detail.map;
}
</script>
</head>
<body>
<!-- <div class="indicium-logo"></div> -->
<div id="content">
<section id="file-reader">
<br></br>
<log-file-reader id="log-file-reader" onchange="globalDataUpload(event)"></log-file-reader>
<br></br>
</section>
<div class="stats-panel">
<section id="stats"><h2>Stats</h2></section>
</div>
<timeline-panel id="timeline-panel" onchange="globalDataChanged(event)"></timeline-panel>
<map-panel id="map-panel" onclick="globalSearchBarEvent(event)"></map-panel>
<ic-panel id="ic-panel" onchange="globalDataChanged(event)"></ic-panel>
</div>
<div id="instructions">
<h2>Instructions</h2>
<p>Unified web interface for analyzing the trace information of the Maps/ICs</p>
<ul>
<li> Visualize Map trees that have gathered</li>
<li><code> /path/to/d8 --trace-maps $FILE</code></li>
<li> Visualize IC events that have gathered</li>
<li><code> /path/to/d8 --trace_ic $FILE (your_script.js) </code></li>
</ul>
</div>
</body>
</html>
// 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.
'use strict';
// TODO(zc) make imports work
// import "./helper.js";
import '../splaytree.js';
import '../codemap.js';
import '../csvparser.js';
import '../consarray.js';
import '../profile.js';
import '../profile_view.js';
import '../logreader.js';
import '../arguments.js';
import '../SourceMap.js';
import './map-processor.js';
import './ic-processor.js';
import './map-model.js';
define(Array.prototype, 'histogram', function(mapFn) {
let histogram = [];
for (let i = 0; i < this.length; i++) {
let value = this[i];
let index = Math.round(mapFn(value))
let bucket = histogram[index];
if (bucket !== undefined) {
bucket.push(value);
} else {
histogram[index] = [value];
}
}
for (let i = 0; i < histogram.length; i++) {
histogram[i] = histogram[i] || [];
}
return histogram;
});
Object.defineProperty(Edge.prototype, 'getColor', {
value: function() {
return transitionTypeToColor(this.type);
}
});
// ===================================
// Controller logic of the application
// Event handlers
document.onkeydown = handleKeyDown;
function handleKeyDown(event) {
stateGlobal.navigation = document.state.navigation;
let nav = document.state.navigation;
switch (event.key) {
case 'ArrowUp':
event.preventDefault();
if (event.shiftKey) {
nav.selectPrevEdge();
} else {
nav.moveInChunk(-1);
}
return false;
case 'ArrowDown':
event.preventDefault();
if (event.shiftKey) {
nav.selectNextEdge();
} else {
nav.moveInChunk(1);
}
return false;
case 'ArrowLeft':
nav.moveInChunks(false);
break;
case 'ArrowRight':
nav.moveInChunks(true);
break;
case '+':
nav.increaseTimelineResolution();
break;
case '-':
nav.decreaseTimelineResolution();
break;
}
}
// Update application state
function updateDocumentState() {
document.state = stateGlobal.state;
try {
document.state.timeline = stateGlobal.timeline;
} catch (error) {
console.log(error);
console.log('cannot assign timeline to state!');
}
}
// Map event log processing
function handleLoadTextMapProcessor(text) {
let mapProcessor = new MapProcessor();
return mapProcessor.processString(text);
}
// IC event file reading and log processing
/*
function loadFileIC(file) {
let reader = new FileReader();
reader.onload = function(evt) {
let icProcessor = new CustomIcProcessor();
icProcessor.processString(this.result);
entries = icProcessor.entries;
$('ic-panel').countSelect.innerHTML = entries.length;
$('ic-panel').updateTable(entries);
} reader.readAsText(file);
$('ic-panel').initGroupKeySelect();
}
*/
function $(id) {
return document.querySelector(id);
}
// holds the state of the application
let stateGlobal = Object.create(null);
// call when a new file uploaded
function globalDataUpload(e) {
stateGlobal.timeline = e.detail;
if (!e.detail) return;
// instantiate the app logic
stateGlobal.fileData = e.detail;
stateGlobal.state = new State();
stateGlobal.timeline = handleLoadTextMapProcessor(stateGlobal.fileData.chunk);
updateDocumentState();
// process the IC explorer
loadFileIC(stateGlobal.fileData.file);
}
function globalSearchBarEvent(e) {
if (!e.detail.isValidMap) return;
document.state.map = e.detail.map;
}
<!-- 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. -->
<style>
#fileReader {
width: 100%;
height: 100px;
line-height: 100px;
text-align: center;
border: solid 1px #000000;
border-radius: 5px;
cursor: pointer;
transition: all 0.5s ease-in-out;
}
#fileReader.done {
height: 20px;
line-height: 20px;
}
#fileReader:hover {
background-color: #e0edfe ;
color: black;
}
.loading #fileReader {
cursor: wait;
}
#fileReader > input {
display: none;
}
#loader {
display: none;
}
.loading #loader {
display: block;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
}
#spinner {
position: absolute;
width: 100px;
height: 100px;
top: 40%;
left: 50%;
margin-left: -50px;
border: 30px solid #000;
border-top: 30px solid #36E;
border-radius: 50%;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<section id="fileReaderSection">
<div id="fileReader" tabindex=1 >
<span id="label">
Drag and drop a v8.log file into this area, or click to choose from disk.
</span>
<input id="file" type="file" name="file">
</div>
<div id="loader">
<div id="spinner"></div>
</div>
</section>
// 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.
defineCustomElement('log-file-reader', (templateText) =>
class LogFileReader extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = 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));
}
$(id) {
return this.shadowRoot.querySelector(id);
}
get section() {
return this.$('#fileReaderSection');
}
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();
var host = event.dataTransfer ? event.dataTransfer : event.target;
this.readFile(host.files[0]);
}
handleDragOver(event) {
event.preventDefault();
}
connectedCallback() {
this.$('#fileReader').focus();
}
readFile(file) {
if (!file) {
this.updateLabel('Failed to load file.');
return;
}
this.$('#fileReader').blur();
this.section.className = 'loading';
const reader = new FileReader();
reader.onload = (e) => {
try {
let dataModel = Object.create(null);
dataModel.file = file;
dataModel.chunk = e.target.result;
this.updateLabel('Finished loading \'' + file.name + '\'.');
this.dispatchEvent(new CustomEvent(
'change', {bubbles: true, composed: true, detail: dataModel}));
this.section.className = 'success';
this.$('#fileReader').classList.add('done');
} catch (err) {
console.error(err);
this.section.className = 'failure';
}
};
// Delay the loading a bit to allow for CSS animations to happen.
setTimeout(() => reader.readAsText(file), 0);
}
});
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. -->
<style>
.map-panel {
background-color: #355EC2;
padding: 20px 20px 20px 20px ;
margin: auto;
}
#map-panel {
background-color: #232323;
padding: 10px 10px 10px 10px ;
margin: auto;
}
#mapDetails {
font-family: monospace;
white-space: pre;
overflow-x: scroll;
}
#transitionView {
overflow-x: scroll;
white-space: nowrap;
min-height: 50px;
max-height: 200px;
padding: 50px 0 0 0;
margin-top: -25px;
width: 100%;
}
.map {
width: 20px;
height: 20px;
display: inline-block;
border-radius: 50%;
background-color: black;
border: 4px solid white;
font-size: 10px;
text-align: center;
line-height: 18px;
color: white;
vertical-align: top;
margin-top: -13px;
/* raise z-index */
position: relative;
z-index: 2;
cursor: pointer;
}
.map.selected {
border-color: black;
}
.transitions {
display: inline-block;
margin-left: -15px;
}
.transition {
min-height: 55px;
margin: 0 0 -2px 2px;
}
/* gray out deprecated transitions */
.deprecated > .transitionEdge,
.deprecated > .map {
opacity: 0.5;
}
.deprecated > .transition {
border-color: rgba(0, 0, 0, 0.5);
}
/* Show a border for all but the first transition */
.transition:nth-of-type(2),
.transition:nth-last-of-type(n+2) {
border-left: 2px solid;
margin-left: 0px;
}
/* special case for 2 transitions */
.transition:nth-last-of-type(1) {
border-left: none;
}
/* topmost transitions are not related */
#transitionView > .transition {
border-left: none;
}
/* topmost transition edge needs initial offset to be aligned */
#transitionView > .transition > .transitionEdge {
margin-left: 13px;
}
.transitionEdge {
height: 2px;
width: 80px;
display: inline-block;
margin: 0 0 2px 0;
background-color: black;
vertical-align: top;
padding-left: 15px;
}
.transitionLabel {
color: black;
transform: rotate(-15deg);
transform-origin: top left;
margin-top: -10px;
font-size: 10px;
white-space: normal;
word-break: break-all;
background-color: rgba(255,255,255,0.5);
}
.black{
background-color: black;
}
.red {
background-color: red;
}
.green {
background-color: green;
}
.yellow {
background-color: yellow;
color: black;
}
.blue {
background-color: blue;
}
.orange {
background-color: orange;
}
.violet {
background-color: violet;
color: black;
}
.showSubtransitions {
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 10px solid black;
cursor: zoom-in;
margin: 4px 0 0 4px;
}
.showSubtransitions.opened {
border-top: none;
border-bottom: 10px solid black;
cursor: zoom-out;
}
#tooltip {
position: absolute;
width: 10px;
height: 10px;
background-color: red;
pointer-events: none;
z-index: 100;
display: none;
}
#searchBarInput {
width: 200px;
}
</style>
<div class="map-panel">
<section id="map-panel">
<h2>Transitions</h2>
<section id="transitionView"></section>
<br/>
<h2>Search Map by Address</h2>
<section id="searchBar"></section>
<input type="search" id="searchBarInput" placeholder="Search maps by address.."></input>
<button id="searchBarBtn">Search</button>
<h2>Selected Map</h2>
<section id="mapDetails"></section>
<div id="tooltip">
<div id="tooltipContents"></div>
</div>
</section>
</div>
// 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.
defineCustomElement('map-panel', (templateText) =>
class MapPanel extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
this.transitionViewSelect.addEventListener(
'mousemove', e => this.handleTransitionViewChange(e));
this.$('#searchBarBtn').addEventListener(
'click', e => this.handleSearchBar(e));
}
$(id) {
return this.shadowRoot.querySelector(id);
}
querySelectorAll(query) {
return this.shadowRoot.querySelectorAll(query);
}
get transitionViewSelect() {
return this.$('#transitionView');
}
get searchBarSelect() {
return this.$('#searchBar');
}
get mapDetailsSelect() {
return this.$('#mapDetails');
}
get tooltipSelect() {
return this.$('#tooltip');
}
get tooltipContentsSelect() {
return this.$('#tooltipContents');
}
handleTransitionViewChange(e){
this.tooltipSelect.style.left = e.pageX + "px";
this.tooltipSelect.style.top = e.pageY + "px";
let map = e.target.map;
if (map) {
this.tooltipContentsSelect.innerText = map.description;
}
}
handleSearchBar(e){
let dataModel = Object.create(null);
let searchBar = this.$('#searchBarInput');
let searchBarInput = searchBar.value;
//access the map from model cache
let selectedMap = V8Map.get(searchBarInput);
if(selectedMap){
dataModel.isValidMap = true;
dataModel.map = selectedMap;
searchBar.className = "green";
} else {
dataModel.isValidMap = false;
searchBar.className = "red";
}
this.dispatchEvent(new CustomEvent(
'click', {bubbles: true, composed: true, detail: dataModel}));
}
});
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. -->
<style>
.timeline-panel {
background-color: #355EC2;
padding: 20px 20px 20px 20px ;
margin: auto;
}
#timeline-panel {
background-color: #232323;
padding: 10px 10px 10px 10px ;
margin: auto;
}
#timeline {
position: relative;
height: 300px;
overflow-y: hidden;
overflow-x: scroll;
user-select: none;
background-color: whitesmoke;
}
#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 white 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 black dashed;
padding-left: 4px;
position: absolute;
pointer-events: none;
font-size: 10px;
opacity: 0.5;
}
#timelineOverview {
width: 100%;
height: 50px;
position: relative;
margin-top: -50px;
margin-bottom: 10px;
background-size: 100% 100%;
border: 1px black solid;
border-width: 1px 0 1px 0;
overflow: hidden;
}
#timelineOverviewIndicator {
height: 100%;
position: absolute;
box-shadow: 0px 2px 20px -5px black inset;
top: 0px;
cursor: ew-resize;
}
#timelineOverviewIndicator .leftMask,
#timelineOverviewIndicator .rightMask {
background-color: rgba(200, 200, 200, 0.5);
width: 10000px;
height: 100%;
position: absolute;
top: 0px;
}
#timelineOverviewIndicator .leftMask {
right: 100%;
}
#timelineOverviewIndicator .rightMask {
left: 100%;
}
</style>
<div class="timeline-panel">
<section id="timeline-panel">
<h2>Timeline</h2>
<div id="timeline">
<div id="timelineLabel">Frequency</div>
<div id="timelineChunks"></div>
<canvas id="timelineCanvas"></canvas>
</div>
<div id="timelineOverview">
<div id="timelineOverviewIndicator">
<div class="leftMask"></div>
<div class="rightMask"></div>
</div>
</div>
</section>
</div>
// 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.
defineCustomElement('timeline-panel', (templateText) =>
class TimelinePanel extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
this.timelineOverviewSelect.addEventListener(
'mousemove', e => this.handleTimelineIndicatorMove(e));
}
$(id) {
return this.shadowRoot.querySelector(id);
}
querySelectorAll(query) {
return this.shadowRoot.querySelectorAll(query);
}
get timelineOverviewSelect() {
return this.$('#timelineOverview');
}
get timelineOverviewIndicatorSelect() {
return this.$('#timelineOverviewIndicator');
}
get timelineCanvasSelect() {
return this.$('#timelineCanvas');
}
get timelineChunksSelect() {
return this.$('#timelineChunks');
}
get timelineSelect() {
return this.$('#timeline');
}
handleTimelineIndicatorMove(event) {
if (event.buttons == 0) return;
let timelineTotalWidth = this.timelineCanvasSelect.offsetWidth;
let factor = this.timelineOverviewSelect.offsetWidth / timelineTotalWidth;
this.timelineSelect.scrollLeft += event.movementX / factor;
}
});
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