// 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 {GB, MB} from '../js/helper.mjs';
import {DOM} from '../js/web-api-helper.mjs';
import {getColorFromSpaceName, kSpaceNames} from './space-categories.mjs';
DOM.defineCustomElement('heap-layout-viewer',
(templateText) =>
class HeapLayoutViewer extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
this.chart = echarts.init(this.$('#chart'), null, {
renderer: 'canvas',
});
window.addEventListener('resize', () => {
this.chart.resize();
});
this.currentIndex = 0;
}
$(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.drawChart(0);
}
getChartTitle(index) {
return this.data[index].header;
}
getSeriesData(pageinfos) {
let ret = [];
for (let pageinfo of pageinfos) {
ret.push({value: pageinfo});
}
return ret;
}
getChartSeries(index) {
const snapshot = this.data[index];
let series = [];
for (const [space_name, pageinfos] of Object.entries(snapshot.data)) {
let space_series = {
name: space_name,
type: 'custom',
renderItem(params, api) {
const addressBegin = api.value(1);
const addressEnd = api.value(2);
const allocated = api.value(3);
const start = api.coord([addressBegin, 0]);
const end = api.coord([addressEnd, 0]);
const allocatedRate = allocated / (addressEnd - addressBegin);
const unAllocatedRate = 1 - allocatedRate;
const standardH = api.size([0, 1])[1];
const standardY = start[1] - standardH / 2;
const allocatedY = standardY + standardH * unAllocatedRate;
const allocatedH = standardH * allocatedRate;
const unAllocatedY = standardY;
const unAllocatedH = standardH - allocatedH;
const allocatedShape = echarts.graphic.clipRectByRect(
{
x: start[0],
y: allocatedY,
width: end[0] - start[0],
height: allocatedH,
},
{
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height,
});
const unAllocatedShape = echarts.graphic.clipRectByRect(
{
x: start[0],
y: unAllocatedY,
width: end[0] - start[0],
height: unAllocatedH,
},
{
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height,
});
const ret = {
type: 'group',
children: [
{
type: 'rect',
shape: allocatedShape,
style: api.style(),
},
{
type: 'rect',
shape: unAllocatedShape,
style: {
fill: '#000000',
},
},
],
};
return ret;
},
data: this.getSeriesData(pageinfos),
encode: {
x: [1, 2],
},
itemStyle: {
color: getColorFromSpaceName(space_name),
},
};
series.push(space_series);
}
return series;
}
drawChart(index) {
if (index >= this.data.length || index < 0) {
console.error('Invalid index:', index);
return;
}
const option = {
tooltip: {
formatter(params) {
const ret = params.marker + params.value[0] + '
' +
'address:' + (params.value[1] / MB).toFixed(3) + 'MB' +
'
' +
'size:' + ((params.value[2] - params.value[1]) / MB).toFixed(3) +
'MB' +
'
' +
'allocated:' + (params.value[3] / MB).toFixed(3) + 'MB' +
'
' +
'wasted:' + params.value[4] + 'B';
return ret;
},
},
grid: {
bottom: 120,
top: 120,
},
dataZoom: [
{
type: 'slider',
filterMode: 'weakFilter',
showDataShadow: true,
labelFormatter: '',
},
{
type: 'inside',
filterMode: 'weakFilter',
},
],
legend: {
show: true,
data: kSpaceNames,
top: '6%',
type: 'scroll',
},
title: {
text: this.getChartTitle(index),
left: 'center',
},
xAxis: {
name: 'Address offset in heap(MB)',
nameLocation: 'center',
nameTextStyle: {
fontSize: 25,
padding: [30, 0, 50, 0],
},
type: 'value',
min: 0,
max: 4 * GB,
axisLabel: {
rotate: 0,
formatter(value, index) {
value = value / MB;
value = value.toFixed(3);
return value;
},
},
},
yAxis: {
data: ['Page'],
},
series: this.getChartSeries(index),
};
this.show();
this.chart.resize();
this.chart.setOption(option);
this.currentIndex = index;
}
});