Commit 2afb2dcd authored by Zeynep Cankara's avatar Zeynep Cankara Committed by Commit Bot

[tools][system-analyzer] Add stats table to timeline-tracks

This CL adds a table to the right side of the each
timeline-tracks to display statistics about the log
events. Double clicking on an event type notifies other
panels about the selected log events with the selected type.

Bug: v8:10644

Change-Id: Iae523d46da4f0b6a007b02a2beac23d9c48aca02
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2353457
Commit-Queue: Zeynep Cankara <zcankara@google.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69498}
parent db15c5e3
......@@ -26,9 +26,9 @@ function defineCustomElement(path, generator) {
let name = path.substring(path.lastIndexOf("/") + 1, path.length);
path = path + '-template.html';
fetch(path)
.then(stream => stream.text())
.then(
templateText => customElements.define(name, generator(templateText)));
.then(stream => stream.text())
.then(
templateText => customElements.define(name, generator(templateText)));
}
// DOM Helpers
......@@ -104,7 +104,7 @@ class CSSColor {
}
function transitionTypeToColor(type) {
function typeToColor(type) {
switch (type) {
case 'new':
return CSSColor.green;
......@@ -149,7 +149,7 @@ function div(classes) {
class V8CustomElement extends HTMLElement {
constructor(templateText) {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = templateText;
}
$(id) {
......@@ -160,7 +160,7 @@ class V8CustomElement extends HTMLElement {
return this.shadowRoot.querySelectorAll(query);
}
div(classes) {return div(classes)}
div(classes) { return div(classes) }
table(className) {
let node = document.createElement('table')
......@@ -178,12 +178,14 @@ class V8CustomElement extends HTMLElement {
return node;
}
tr(){
tr() {
return document.createElement('tr');
}
removeAllChildren(node) { return removeAllChildren(node); }
}
export {defineCustomElement, V8CustomElement, removeAllChildren,
$, div, transitionTypeToColor, CSSColor};
export {
defineCustomElement, V8CustomElement, removeAllChildren,
$, div, typeToColor, CSSColor
};
......@@ -2,19 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {transitionTypeToColor} from './helper.mjs';
import {Timeline} from './timeline.mjs';
import { typeToColor } from './helper.mjs';
import { Timeline } from './timeline.mjs';
// ===========================================================================
import {Event} from './event.mjs';
import { Event } from './event.mjs';
const kChunkHeight = 250;
const kChunkWidth = 10;
function define(prototype, name, fn) {
Object.defineProperty(prototype, name, {value: fn, enumerable: false});
Object.defineProperty(prototype, name, { value: fn, enumerable: false });
}
define(Array.prototype, 'max', function(fn) {
define(Array.prototype, 'max', function (fn) {
if (this.length === 0) return undefined;
if (fn === undefined) fn = (each) => each;
let max = fn(this[0]);
......@@ -23,10 +23,10 @@ define(Array.prototype, 'max', function(fn) {
}
return max;
})
define(Array.prototype, 'first', function() {
define(Array.prototype, 'first', function () {
return this[0]
});
define(Array.prototype, 'last', function() {
define(Array.prototype, 'last', function () {
return this[this.length - 1]
});
// ===========================================================================
......@@ -57,12 +57,12 @@ class MapProcessor extends LogReader {
'code-move': {
parsers: [parseInt, parseInt],
'sfi-move':
{parsers: [parseInt, parseInt], processor: this.processCodeMove},
'code-delete': {parsers: [parseInt], processor: this.processCodeDelete},
{ parsers: [parseInt, parseInt], processor: this.processCodeMove },
'code-delete': { parsers: [parseInt], processor: this.processCodeDelete },
processor: this.processFunctionMove
},
'map-create':
{parsers: [parseInt, parseString], processor: this.processMapCreate},
{ parsers: [parseInt, parseString], processor: this.processMapCreate },
'map': {
parsers: [
parseString, parseInt, parseString, parseString, parseInt, parseInt,
......@@ -116,8 +116,8 @@ class MapProcessor extends LogReader {
}
} catch (e) {
console.error(
'Error occurred during parsing line ' + i +
', trying to continue: ' + e);
'Error occurred during parsing line ' + i +
', trying to continue: ' + e);
}
return this.finalize();
}
......@@ -165,19 +165,19 @@ class MapProcessor extends LogReader {
let funcAddr = parseInt(maybe_func[0]);
let state = this.parseState(maybe_func[1]);
this.#profile.addFuncCode(
type, name, timestamp, start, size, funcAddr, state);
type, name, timestamp, start, size, funcAddr, state);
} else {
this.#profile.addCode(type, name, timestamp, start, size);
}
}
processV8Version(majorVersion, minorVersion){
if(
processV8Version(majorVersion, minorVersion) {
if (
(majorVersion == this.MAJOR_VERSION && minorVersion <= this.MINOR_VERSION)
|| (majorVersion < this.MAJOR_VERSION)){
window.alert(
`Unsupported version ${majorVersion}.${minorVersion}. \n` +
`Please use the matching tool for given the V8 version.`);
|| (majorVersion < this.MAJOR_VERSION)) {
window.alert(
`Unsupported version ${majorVersion}.${minorVersion}. \n` +
`Please use the matching tool for given the V8 version.`);
}
}
......@@ -196,9 +196,9 @@ class MapProcessor extends LogReader {
formatPC(pc, line, column) {
let entry = this.#profile.findEntry(pc);
if (!entry) return '<unknown>'
if (entry.type === 'Builtin') {
return entry.name;
}
if (entry.type === 'Builtin') {
return entry.name;
}
let name = entry.func.getName();
let array = this.#formatPCRegexp.exec(name);
if (array === null) {
......@@ -262,15 +262,15 @@ class MapLogEvent extends Event {
// TODO(zcankara): Change this to private class field.
#isDeprecated = false;
deprecatedTargets = null;
leftId= 0;
leftId = 0;
rightId = 0;
filePosition = '';
id = -1;
constructor(id, time) {
if (!time) throw new Error('Invalid time');
super(id, time);
MapLogEvent.set(id, this);
this.id = id;
if (!time) throw new Error('Invalid time');
super(id, time);
MapLogEvent.set(id, this);
this.id = id;
}
finalizeRootMap(id) {
......@@ -407,7 +407,7 @@ class Edge {
}
getColor() {
return transitionTypeToColor(this.type);
return typeToColor(this.type);
}
finishSetup() {
......@@ -509,7 +509,7 @@ class Edge {
return this.type + ' ' + this.symbol() + this.name;
}
return this.type + ' ' + (this.reason ? this.reason : '') + ' ' +
(this.name ? this.name : '')
(this.name ? this.name : '')
}
}
......@@ -519,7 +519,7 @@ class ArgumentsProcessor extends BaseArgumentsProcessor {
getArgsDispatch() {
return {
'--range':
['range', 'auto,auto', 'Specify the range limit as [start],[end]'],
['range', 'auto,auto', 'Specify the range limit as [start],[end]'],
'--source-map': [
'sourceMap', null,
'Specify the source map that should be used for output'
......@@ -535,4 +535,4 @@ class ArgumentsProcessor extends BaseArgumentsProcessor {
}
}
export { MapProcessor, MapLogEvent, kChunkWidth, kChunkHeight};
export { MapProcessor, MapLogEvent, kChunkWidth, kChunkHeight };
......@@ -65,27 +65,41 @@ found in the LICENSE file. -->
opacity: 0.5;
}
#timelineLegend {
#legend {
position: relative;
float: right;
text-align: center;
width: 100%;
max-width: 280px;
padding-left: 20px;
padding-top: 10px;
}
.timeline {
background-color: var(--timeline-background-color);
th,
td {
width: 200px;
text-align: center;
padding: 5px;
}
#timelineLegendContent {
float: right;
padding: 20px;
width: 200px;
.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 id="legend">
<table>
<thead>
<tr>
<td>Color</td>
<td>Type</td>
<td>Count</td>
<td>Percent</td>
</tr>
</thead>
<tbody id="legendContent">
</tbody>
</table>
</div>
<div id="timeline">
<div id="timelineLabel">Frequency</div>
......
......@@ -4,7 +4,7 @@
import {
defineCustomElement, V8CustomElement,
transitionTypeToColor, CSSColor
typeToColor, CSSColor
} from '../helper.mjs';
import { kChunkWidth, kChunkHeight } from '../map-processor.mjs';
import { SelectionEvent, FocusEvent, SelectTimeEvent } from '../events.mjs';
......@@ -41,15 +41,18 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
return this.$('#timeline');
}
get timelineLegendContent() {
return this.$('#timelineLegendContent');
get timelineLegend() {
return this.$('#legend');
}
get timelineLegendContent() {
return this.$('#legendContent');
}
set data(value) {
this.#timeline = value;
this.updateChunks();
this.updateTimeline();
this.updateStats();
this.updateLegend();
}
get data() {
......@@ -81,42 +84,58 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
set scrollLeft(offset) {
this.timeline.scrollLeft = offset;
}
updateStats() {
let unique = new Map();
//TODO(zcankara) Carry to the Model, nothing UI related
updateLegend() {
const uniqueTypes = new Map();
for (const entry of this.data.all) {
if (!unique.has(entry.type)) {
unique.set(entry.type, [entry]);
if (!uniqueTypes.has(entry.type)) {
uniqueTypes.set(entry.type, [entry]);
} else {
unique.get(entry.type).push(entry);
uniqueTypes.get(entry.type).push(entry);
}
}
this.renderStatsWindow(unique);
this.renderLegend(uniqueTypes);
}
renderStatsWindow(unique) {
renderLegend(uniqueTypes) {
let timelineLegend = this.timelineLegend;
let timelineLegendContent = this.timelineLegendContent;
this.removeAllChildren(timelineLegendContent);
let fragment = document.createDocumentFragment();
let row = this.tr();
row.entries = this.data.all;
row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
row.appendChild(this.td(""));
let td = this.td("All");
row.appendChild(td);
row.appendChild(this.td(this.data.all.length));
row.appendChild(this.td("100%"));
timelineLegendContent.appendChild(row);
let colorIterator = 0;
unique.forEach((entries, type) => {
let dt = document.createElement("dt");
dt.innerHTML = entries.length;
dt.style.backgroundColor = transitionTypeToColor(type);
dt.style.color = CSSColor.surfaceColor;
fragment.appendChild(dt);
let dd = document.createElement("dd");
dd.innerHTML = type;
dd.entries = entries;
dd.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
fragment.appendChild(dd);
uniqueTypes.forEach((entries, type) => {
let row = this.tr();
row.entries = entries;
row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
let color = typeToColor(type);
if (color !== null) {
let div = this.div(["colorbox"]);
div.style.backgroundColor = color;
row.appendChild(this.td(div));
} else {
row.appendChild(this.td(""));
}
let td = this.td(type);
row.appendChild(td);
row.appendChild(this.td(entries.length));
let percent = (entries.length / this.data.all.length) * 100;
row.appendChild(this.td(percent.toFixed(1) + "%"));
timelineLegendContent.appendChild(row);
colorIterator += 1;
});
timelineLegendContent.appendChild(fragment);
timelineLegend.appendChild(timelineLegendContent);
}
handleEntryTypeDblClick(e) {
this.dispatchEvent(new SelectionEvent(e.target.entries));
this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries));
}
timelineIndicatorMove(offset) {
......@@ -179,14 +198,14 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
let type, count;
if (true) {
chunk.getBreakdown(map => map.type).forEach(([type, count]) => {
ctx.fillStyle = transitionTypeToColor(type);
ctx.fillStyle = typeToColor(type);
let height = count / total * kHeight;
ctx.fillRect(0, y, kWidth, y + height);
y += height;
});
} else {
chunk.items.forEach(map => {
ctx.fillStyle = transitionTypeToColor(map.type);
ctx.fillStyle = typeToColor(map.type);
let y = chunk.yOffset(map);
ctx.fillRect(0, y, kWidth, y + 1);
});
......@@ -313,7 +332,7 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
}
setEdgeStyle(edge, ctx) {
let color = transitionTypeToColor(edge.type);
let color = typeToColor(edge.type);
ctx.strokeStyle = color;
ctx.fillStyle = color;
}
......
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