v8/tools/system-analyzer/timeline.mjs
Zeynep Cankara a73ffca3a4 [tools][system-analyzer] Find Unique IC/Map types and improve Map panel
This CL enables showing map details of the selected map coming from
FocusEvent. It also improves UI experience of selecting a map from
map transitions, highlighting selected map.

Additionally, stores information about unique map/IC events in model
for the timeline-track legend.

Bug: v8:10644
Change-Id: Ieb8a2ac0bf1af282d55bce18130192d7178538da
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2387564
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Sathya Gunasekaran  <gsathya@chromium.org>
Commit-Queue: Zeynep Cankara <zcankara@google.com>
Cr-Commit-Position: refs/heads/master@{#69673}
2020-09-02 13:07:40 +00:00

258 lines
5.5 KiB
JavaScript

// 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.
class Timeline {
#values;
#selection;
#uniqueTypes;
constructor() {
this.#values = [];
this.startTime = 0;
this.endTime = 0;
}
get all() {
return this.#values;
}
get selection() {
return this.#selection;
}
set selection(value) {
this.#selection = value;
}
selectTimeRange(start, end) {
this.#selection = this.filter(
e => e.time >= start && e.time <= end);
}
getChunks(windowSizeMs) {
//TODO(zcankara) Fill this one
return this.chunkSizes(windowSizeMs);
}
get values() {
//TODO(zcankara) Not to break something delete later
return this.#values;
}
count(filter) {
return this.all.reduce((sum, each) => {
return sum + (filter(each) === true ? 1 : 0);
}, 0);
}
filter(predicate) {
return this.all.filter(predicate);
}
push(event) {
let time = event.time;
if (!this.isEmpty() && this.last().time > time) {
// Invalid insertion order, might happen without --single-process,
// finding insertion point.
let insertionPoint = this.find(time);
this.#values.splice(insertionPoint, event);
} else {
this.#values.push(event);
}
if (time > 0) {
this.endTime = Math.max(this.endTime, time);
if (this.startTime === 0) {
this.startTime = time;
} else {
this.startTime = Math.min(this.startTime, time);
}
}
}
at(index) {
return this.#values[index];
}
isEmpty() {
return this.size() === 0;
}
size() {
return this.#values.length;
}
first() {
return this.#values.first();
}
last() {
return this.#values.last();
}
duration() {
return this.last().time - this.first().time;
}
groupByTypes() {
this.#uniqueTypes = new Map();
for (const entry of this.all) {
if (!this.#uniqueTypes.has(entry.type)) {
this.#uniqueTypes.set(entry.type, [entry]);
} else {
this.#uniqueTypes.get(entry.type).push(entry);
}
}
}
get uniqueTypes() {
if (this.#uniqueTypes === undefined) {
this.groupByTypes();
}
return this.#uniqueTypes;
}
forEachChunkSize(count, fn) {
const increment = this.duration() / count;
let currentTime = this.first().time + increment;
let index = 0;
for (let i = 0; i < count; i++) {
let nextIndex = this.find(currentTime, index);
let nextTime = currentTime + increment;
fn(index, nextIndex, currentTime, nextTime);
index = nextIndex;
currentTime = nextTime;
}
}
chunkSizes(count) {
let chunks = [];
this.forEachChunkSize(count, (start, end) => chunks.push(end - start));
return chunks;
}
chunks(count) {
let chunks = [];
this.forEachChunkSize(count, (start, end, startTime, endTime) => {
let items = this.#values.slice(start, end);
chunks.push(new Chunk(chunks.length, startTime, endTime, items));
});
return chunks;
}
range(start, end) {
const first = this.find(start);
if (first < 0) return [];
const last = this.find(end, first);
return this.#values.slice(first, last);
}
find(time, offset = 0) {
return this.#find(this.#values, each => each.time - time, offset);
}
#find(array, cmp, offset = 0) {
let min = offset;
let max = array.length;
while (min < max) {
let mid = min + Math.floor((max - min) / 2);
let result = cmp(array[mid]);
if (result > 0) {
max = mid - 1;
} else {
min = mid + 1;
}
}
return min;
}
depthHistogram() {
return this.#values.histogram(each => each.depth);
}
fanOutHistogram() {
return this.#values.histogram(each => each.children.length);
}
forEach(fn) {
return this.#values.forEach(fn);
}
}
// ===========================================================================
class Chunk {
constructor(index, start, end, items) {
this.index = index;
this.start = start;
this.end = end;
this.items = items;
this.height = 0;
}
isEmpty() {
return this.items.length === 0;
}
last() {
return this.at(this.size() - 1);
}
first() {
return this.at(0);
}
at(index) {
return this.items[index];
}
size() {
return this.items.length;
}
yOffset(event) {
// items[0] == oldest event, displayed at the top of the chunk
// items[n-1] == youngest event, displayed at the bottom of the chunk
return (1 - (this.indexOf(event) + 0.5) / this.size()) * this.height;
}
indexOf(event) {
return this.items.indexOf(event);
}
has(event) {
if (this.isEmpty()) return false;
return this.first().time <= event.time && event.time <= this.last().time;
}
next(chunks) {
return this.findChunk(chunks, 1);
}
prev(chunks) {
return this.findChunk(chunks, -1);
}
findChunk(chunks, delta) {
let i = this.index + delta;
let chunk = chunks[i];
while (chunk && chunk.size() === 0) {
i += delta;
chunk = chunks[i];
}
return chunk;
}
getBreakdown(event_fn) {
if (event_fn === void 0) {
event_fn = each => each;
}
let breakdown = { __proto__: null };
this.items.forEach(each => {
const type = event_fn(each);
const v = breakdown[type];
breakdown[type] = (v | 0) + 1;
});
return Object.entries(breakdown).sort((a, b) => a[1] - b[1]);
}
filter() {
return this.items.filter(map => !map.parent() || !this.has(map.parent()));
}
}
export { Timeline, Chunk };