v8/tools/heap-layout/heap-size-trend-viewer.mjs
JianxiaoLuIntel 257b0a43ac [tool] heap layout trace file visualization tool
Design doc:
https://docs.google.com/document/d/1rxM3sDd-ZiOLznqw7MvYraulAPWJSVqC_CztO4YpUTQ/edit

Change-Id: I471ff31f32b7bdd22cb03005c1dcc18aa485ad77
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3313793
Auto-Submit: Jianxiao Lu <jianxiao.lu@intel.com>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Shiyu Zhang <shiyu.zhang@intel.com>
Cr-Commit-Position: refs/heads/main@{#78428}
2021-12-22 02:07:35 +00:00

267 lines
7.1 KiB
JavaScript

// Copyright 2021 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 {MB} from '../js/helper.mjs';
import {DOM} from '../js/web-api-helper.mjs';
import {getColorFromSpaceName, kSpaceNames} from './space-categories.mjs';
class TrendLineHelper {
static re_gc_count = /(?<=(Before|After) GC:)\d+(?=,)/;
static re_allocated = /allocated/;
static re_space_name = /^[a-z_]+_space/;
static snapshotHeaderToXLabel(header) {
const gc_count = this.re_gc_count.exec(header)[0];
const alpha = header[0];
return alpha + gc_count;
}
static getLineSymbolFromTrendLineName(trend_line_name) {
const is_allocated_line = this.re_allocated.test(trend_line_name);
if (is_allocated_line) {
return 'emptyTriangle';
}
return 'emptyCircle';
}
static getSizeTrendLineName(space_name) {
return space_name + ' size';
}
static getAllocatedTrendSizeName(space_name) {
return space_name + ' allocated';
}
static getSpaceNameFromTrendLineName(trend_line_name) {
const space_name = this.re_space_name.exec(trend_line_name)[0];
return space_name;
}
}
DOM.defineCustomElement('heap-size-trend-viewer',
(templateText) =>
class HeapSizeTrendViewer extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
this.chart = echarts.init(this.$('#chart'), null, {
renderer: 'canvas',
});
this.chart.getZr().on('click', 'series.line', (params) => {
const pointInPixel = [params.offsetX, params.offsetY];
const pointInGrid =
this.chart.convertFromPixel({seriesIndex: 0}, pointInPixel);
const xIndex = pointInGrid[0];
this.dispatchEvent(new CustomEvent('change', {
bubbles: true,
composed: true,
detail: xIndex,
}));
this.setXMarkLine(xIndex);
});
this.chartXAxisData = null;
this.chartSeriesData = null;
this.currentIndex = 0;
window.addEventListener('resize', () => {
this.chart.resize();
});
}
$(id) {
return this.shadowRoot.querySelector(id);
}
set data(value) {
this._data = value;
this.stateChanged();
}
get data() {
return this._data;
}
hide() {
this.$('#container').style.display = 'none';
}
show() {
this.$('#container').style.display = 'block';
}
stateChanged() {
this.initTrendLineNames();
this.initXAxisDataAndSeries();
this.drawChart();
}
initTrendLineNames() {
this.trend_line_names = [];
for (const space_name of kSpaceNames) {
this.trend_line_names.push(
TrendLineHelper.getSizeTrendLineName(space_name));
this.trend_line_names.push(
TrendLineHelper.getAllocatedTrendSizeName(space_name));
}
}
// X axis represent the moment before or after nth GC : [B1,A1,...Bn,An].
initXAxisDataAndSeries() {
this.chartXAxisData = [];
this.chartSeriesData = [];
let trend_line_name_data_dict = {};
for (const trend_line_name of this.trend_line_names) {
trend_line_name_data_dict[trend_line_name] = [];
}
// Init x axis data and trend line series.
for (const snapshot of this.data) {
this.chartXAxisData.push(
TrendLineHelper.snapshotHeaderToXLabel(snapshot.header));
for (const [space_name, pageinfos] of Object.entries(snapshot.data)) {
const size_trend_line_name =
TrendLineHelper.getSizeTrendLineName(space_name);
const allocated_trend_line_name =
TrendLineHelper.getAllocatedTrendSizeName(space_name);
let size_sum = 0;
let allocated_sum = 0;
for (const pageinfo of pageinfos) {
size_sum += pageinfo[2] - pageinfo[1];
allocated_sum += pageinfo[3];
}
trend_line_name_data_dict[size_trend_line_name].push(size_sum);
trend_line_name_data_dict[allocated_trend_line_name].push(
allocated_sum);
}
}
// Init mark line series as the first series
const markline_series = {
name: 'mark-line',
type: 'line',
markLine: {
silent: true,
symbol: 'none',
label: {
show: false,
},
lineStyle: {
color: '#333',
},
data: [
{
xAxis: 0,
},
],
},
};
this.chartSeriesData.push(markline_series);
for (const [trend_line_name, trend_line_data] of Object.entries(
trend_line_name_data_dict)) {
const color = getColorFromSpaceName(
TrendLineHelper.getSpaceNameFromTrendLineName(trend_line_name));
const trend_line_series = {
name: trend_line_name,
type: 'line',
data: trend_line_data,
lineStyle: {
color: color,
},
itemStyle: {
color: color,
},
symbol: TrendLineHelper.getLineSymbolFromTrendLineName(trend_line_name),
symbolSize: 8,
};
this.chartSeriesData.push(trend_line_series);
}
}
setXMarkLine(index) {
if (index < 0 || index >= this.data.length) {
console.error('Invalid index:', index);
return;
}
// Set the mark-line series
this.chartSeriesData[0].markLine.data[0].xAxis = index;
this.chart.setOption({
series: this.chartSeriesData,
});
this.currentIndex = index;
}
drawChart() {
const option = {
dataZoom: [
{
type: 'inside',
filterMode: 'weakFilter',
},
{
type: 'slider',
filterMode: 'weakFilter',
labelFormatter: '',
},
],
title: {
text: 'Size Trend',
left: 'center',
},
tooltip: {
trigger: 'axis',
position(point, params, dom, rect, size) {
let ret_x = point[0] + 10;
if (point[0] > size.viewSize[0] * 0.7) {
ret_x = point[0] - dom.clientWidth - 10;
}
return [ret_x, '85%'];
},
formatter(params) {
const colorSpan = (color) =>
'<span style="display:inline-block;margin-right:1px;border-radius:5px;width:9px;height:9px;background-color:' +
color + '"></span>';
let result = '<p>' + params[0].axisValue + '</p>';
params.forEach((item) => {
const xx = '<p style="margin:0;">' + colorSpan(item.color) + ' ' +
item.seriesName + ': ' + (item.data / MB).toFixed(2) + 'MB' +
'</p>';
result += xx;
});
return result;
},
},
legend: {
data: this.trend_line_names,
top: '6%',
type: 'scroll',
},
xAxis: {
minInterval: 1,
type: 'category',
boundaryGap: false,
data: this.chartXAxisData,
},
yAxis: {
type: 'value',
axisLabel: {
formatter(value, index) {
return (value / MB).toFixed(3) + 'MB';
},
},
},
series: this.chartSeriesData,
};
this.show();
this.chart.resize();
this.chart.setOption(option);
}
});