// 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; } });