1// Copyright 2021 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import {GB, MB} from '../js/helper.mjs'; 6import {DOM} from '../js/web-api-helper.mjs'; 7 8import {getColorFromSpaceName, kSpaceNames} from './space-categories.mjs'; 9 10DOM.defineCustomElement('heap-layout-viewer', 11 (templateText) => 12 class HeapLayoutViewer extends HTMLElement { 13 constructor() { 14 super(); 15 const shadowRoot = this.attachShadow({mode: 'open'}); 16 shadowRoot.innerHTML = templateText; 17 this.chart = echarts.init(this.$('#chart'), null, { 18 renderer: 'canvas', 19 }); 20 window.addEventListener('resize', () => { 21 this.chart.resize(); 22 }); 23 this.currentIndex = 0; 24 } 25 26 $(id) { 27 return this.shadowRoot.querySelector(id); 28 } 29 30 set data(value) { 31 this._data = value; 32 this.stateChanged(); 33 } 34 35 get data() { 36 return this._data; 37 } 38 39 hide() { 40 this.$('#container').style.display = 'none'; 41 } 42 43 show() { 44 this.$('#container').style.display = 'block'; 45 } 46 47 stateChanged() { 48 this.drawChart(0); 49 } 50 51 getChartTitle(index) { 52 return this.data[index].header; 53 } 54 55 getSeriesData(pageinfos) { 56 let ret = []; 57 for (let pageinfo of pageinfos) { 58 ret.push({value: pageinfo}); 59 } 60 return ret; 61 } 62 63 getChartSeries(index) { 64 const snapshot = this.data[index]; 65 let series = []; 66 for (const [space_name, pageinfos] of Object.entries(snapshot.data)) { 67 let space_series = { 68 name: space_name, 69 type: 'custom', 70 renderItem(params, api) { 71 const addressBegin = api.value(1); 72 const addressEnd = api.value(2); 73 const allocated = api.value(3); 74 const start = api.coord([addressBegin, 0]); 75 const end = api.coord([addressEnd, 0]); 76 77 const allocatedRate = allocated / (addressEnd - addressBegin); 78 const unAllocatedRate = 1 - allocatedRate; 79 80 const standardH = api.size([0, 1])[1]; 81 const standardY = start[1] - standardH / 2; 82 83 const allocatedY = standardY + standardH * unAllocatedRate; 84 const allocatedH = standardH * allocatedRate; 85 86 const unAllocatedY = standardY; 87 const unAllocatedH = standardH - allocatedH; 88 89 const allocatedShape = echarts.graphic.clipRectByRect( 90 { 91 x: start[0], 92 y: allocatedY, 93 width: end[0] - start[0], 94 height: allocatedH, 95 }, 96 { 97 x: params.coordSys.x, 98 y: params.coordSys.y, 99 width: params.coordSys.width, 100 height: params.coordSys.height, 101 }); 102 103 const unAllocatedShape = echarts.graphic.clipRectByRect( 104 { 105 x: start[0], 106 y: unAllocatedY, 107 width: end[0] - start[0], 108 height: unAllocatedH, 109 }, 110 { 111 x: params.coordSys.x, 112 y: params.coordSys.y, 113 width: params.coordSys.width, 114 height: params.coordSys.height, 115 }); 116 117 const ret = { 118 type: 'group', 119 children: [ 120 { 121 type: 'rect', 122 shape: allocatedShape, 123 style: api.style(), 124 }, 125 { 126 type: 'rect', 127 shape: unAllocatedShape, 128 style: { 129 fill: '#000000', 130 }, 131 }, 132 ], 133 }; 134 return ret; 135 }, 136 data: this.getSeriesData(pageinfos), 137 encode: { 138 x: [1, 2], 139 }, 140 itemStyle: { 141 color: getColorFromSpaceName(space_name), 142 }, 143 }; 144 series.push(space_series); 145 } 146 return series; 147 } 148 149 drawChart(index) { 150 if (index >= this.data.length || index < 0) { 151 console.error('Invalid index:', index); 152 return; 153 } 154 const option = { 155 tooltip: { 156 formatter(params) { 157 const ret = params.marker + params.value[0] + '<br>' + 158 'address:' + (params.value[1] / MB).toFixed(3) + 'MB' + 159 '<br>' + 160 'size:' + ((params.value[2] - params.value[1]) / MB).toFixed(3) + 161 'MB' + 162 '<br>' + 163 'allocated:' + (params.value[3] / MB).toFixed(3) + 'MB' + 164 '<br>' + 165 'wasted:' + params.value[4] + 'B'; 166 return ret; 167 }, 168 }, 169 grid: { 170 bottom: 120, 171 top: 120, 172 }, 173 dataZoom: [ 174 { 175 type: 'slider', 176 filterMode: 'weakFilter', 177 showDataShadow: true, 178 labelFormatter: '', 179 }, 180 { 181 type: 'inside', 182 filterMode: 'weakFilter', 183 }, 184 ], 185 legend: { 186 show: true, 187 data: kSpaceNames, 188 top: '6%', 189 type: 'scroll', 190 }, 191 title: { 192 text: this.getChartTitle(index), 193 left: 'center', 194 }, 195 xAxis: { 196 name: 'Address offset in heap(MB)', 197 nameLocation: 'center', 198 nameTextStyle: { 199 fontSize: 25, 200 padding: [30, 0, 50, 0], 201 }, 202 type: 'value', 203 min: 0, 204 max: 4 * GB, 205 axisLabel: { 206 rotate: 0, 207 formatter(value, index) { 208 value = value / MB; 209 value = value.toFixed(3); 210 return value; 211 }, 212 }, 213 }, 214 yAxis: { 215 data: ['Page'], 216 }, 217 series: this.getChartSeries(index), 218 }; 219 220 this.show(); 221 this.chart.resize(); 222 this.chart.setOption(option); 223 this.currentIndex = index; 224 } 225}); 226