v8/tools/system-analyzer/timeline.mjs
Zeynep Cankara dda133ad35 [tools][system-analyzer] Add Generic Event Class
This CL adds a generic Event Class to unify common
methods of IC and Map events. The Entry Class for IC
Events and V8Map Class for Map Events inherits from
this generic Event Class.

Bug: v8:10644, v8:10735

Change-Id: I77d68fb40ee0ffbe297fcd1a13c3e2b746938168
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2317309
Commit-Queue: Zeynep Cankara <zcankara@google.com>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Sathya Gunasekaran  <gsathya@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69066}
2020-07-27 09:22:58 +00:00

239 lines
5.1 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;
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;
}
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};