1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the 'License'); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF unknown KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15import { TabPerfFuncAsmHtml } from './TabPerfFuncAsm.html'; 16import { BaseElement, element } from '../../../../../base-ui/BaseElement'; 17import { LitTable } from '../../../../../base-ui/table/lit-table'; 18import { 19 FormattedAsmInstruction, 20 PerfFunctionAsmParam, 21 OriginAsmInstruction, 22} from '../../../../bean/PerfAnalysis'; 23import { WebSocketManager } from '../../../../../webSocket/WebSocketManager'; 24import { Constants, TypeConstants } from '../../../../../webSocket/Constants'; 25 26@element('tab-perf-func-asm') 27export class TabPerfFuncAsm extends BaseElement { 28 private assmblerTable: LitTable | null | undefined; 29 private loadingElement: HTMLElement | null | undefined; 30 private functionName: string = ''; 31 private totalCount: number = 0; 32 private totalCountElement: HTMLDivElement | null | undefined; 33 private textFileOffElement: HTMLDivElement | null | undefined; 34 private errorMessageElement: HTMLDivElement | null | undefined; 35 private funcBaseAddr: bigint = BigInt(0); 36 // Key: offset; Value: selfcount 37 private funcSampleMap: Map<number, number> = new Map(); 38 private showUpData: FormattedAsmInstruction[] = []; 39 private originalShowUpData: FormattedAsmInstruction[] = []; 40 private formattedAsmIntructionArray: FormattedAsmInstruction[] = []; 41 private resizeObserver: ResizeObserver | null = null; 42 43 initHtml(): string { 44 return TabPerfFuncAsmHtml; 45 } 46 47 initElements(): void { 48 this.assmblerTable = this.shadowRoot!.querySelector<LitTable>( 49 '#perf-function-asm-table' 50 ); 51 this.loadingElement = 52 this.shadowRoot!.querySelector<HTMLElement>('#loading'); 53 this.totalCountElement = 54 this.shadowRoot!.querySelector<HTMLDivElement>('#total-count'); 55 this.textFileOffElement = 56 this.shadowRoot!.querySelector<HTMLDivElement>('#text-file-off'); 57 this.errorMessageElement = this.shadowRoot!.querySelector<HTMLDivElement>('#error-message'); 58 59 this.assmblerTable!.style.display = 'grid'; 60 61 this.assmblerTable!.itemTextHandleMap.set('addr', (value: unknown) => { 62 return `0x${(value as number).toString(16)}`; 63 }); 64 65 this.assmblerTable!.itemTextHandleMap.set('selfcount', (value: unknown) => { 66 return (value as number) === 0 ? '' : (value as number).toString(); 67 }); 68 69 this.assmblerTable!.itemTextHandleMap.set('percent', (value: unknown) => { 70 return (value as number) === 0 ? '' : (value as number).toString(); 71 }); 72 73 this.assmblerTable!.itemTextHandleMap.set('instruction', (value: unknown) => { 74 return (value as string) === '' ? 'INVALID' : (value as string); 75 }); 76 77 this.assmblerTable!.itemTextHandleMap.set('sourceLine', (value: unknown) => { 78 return (value as string) || ''; 79 }); 80 81 this.assmblerTable!.addEventListener('column-click', ((evt: Event) => { 82 const {key, sort} = (evt as CustomEvent).detail; 83 if (key === 'selfcount') { 84 if (sort === 0) { 85 this.assmblerTable!.recycleDataSource = this.originalShowUpData; 86 this.assmblerTable!.reMeauseHeight(); 87 } else { 88 this.showUpData.sort((a, b) => { 89 return sort === 1 90 ? a.selfcount - b.selfcount 91 : b.selfcount - a.selfcount; 92 }); 93 this.assmblerTable!.recycleDataSource = this.showUpData; 94 this.assmblerTable!.reMeauseHeight(); 95 } 96 } else if (key === 'percent') { 97 if (sort === 0) { 98 this.assmblerTable!.recycleDataSource = this.originalShowUpData; 99 this.assmblerTable!.reMeauseHeight(); 100 } else { 101 this.showUpData.sort((a, b) => { 102 return sort === 1 ? a.percent - b.percent : b.percent - a.percent; 103 }); 104 this.assmblerTable!.recycleDataSource = this.showUpData; 105 this.assmblerTable!.reMeauseHeight(); 106 } 107 } 108 }) as EventListener); 109 } 110 111 private updateTotalCount(): void { 112 if (this.functionName) { 113 this.totalCountElement!.innerHTML = `<span class="title-label">Total Count:</span> ${this.totalCount}`; 114 } 115 } 116 117 private showLoading(): void { 118 if (this.loadingElement) { 119 this.loadingElement.removeAttribute('hidden'); 120 } 121 } 122 123 private hideLoading(): void { 124 if (this.loadingElement) { 125 this.loadingElement.setAttribute('hidden', ''); 126 } 127 } 128 129 private showError(message: string): void { 130 if (this.errorMessageElement) { 131 this.errorMessageElement.textContent = message; 132 this.errorMessageElement.style.display = 'block'; 133 } 134 } 135 136 private hideError(): void { 137 if (this.errorMessageElement) { 138 this.errorMessageElement.style.display = 'none'; 139 } 140 } 141 142 set data(data: PerfFunctionAsmParam) { 143 if (this.functionName === data.functionName || data.functionName === undefined) { 144 return; 145 } 146 147 (async (): Promise<void> => { 148 try { 149 this.clearData(); 150 this.functionName = data.functionName; 151 this.totalCount = data.totalCount; 152 this.updateTotalCount(); 153 this.showLoading(); 154 // @ts-ignore 155 const vaddrInFile = data.vaddrList[0].vaddrInFile; 156 // 1. 先转成 BigInt 157 // 2. 用 asUintN 转成无符号64位 158 // 3. 如果需要用作数值运算,再转回 Number 159 this.funcBaseAddr = BigInt.asUintN(64, BigInt(vaddrInFile)); 160 // 1. 计算采样数据 161 this.calculateFuncAsmSapleCount(data.vaddrList); 162 // 2. 等待汇编指令数据 163 let callback: (cmd: number, e: Uint8Array) => void; 164 165 await Promise.race([ 166 new Promise<void>((resolve, reject) => { 167 callback = (cmd: number, e: Uint8Array): void => { 168 try { 169 170 if (cmd === Constants.DISASSEMBLY_QUERY_BACK_CMD) { 171 const result = JSON.parse(new TextDecoder().decode(e)); 172 if (result.resultCode === 0) { 173 if (result.anFileOff) { 174 this.textFileOffElement!.innerHTML = `<span class="title-label">.text section:</span> ${result.anFileOff}`; 175 this.textFileOffElement!.style.display = 'block'; 176 } else { 177 this.textFileOffElement!.style.display = 'none'; 178 } 179 this.formatAsmInstruction(JSON.parse(result.resultMessage)); 180 this.calcutelateShowUpData(); 181 resolve(); 182 } else { 183 reject(new Error(`Failed with code: ${result.resultCode}, error message: ${result.resultMessage}`)); 184 } 185 WebSocketManager.getInstance()?.unregisterCallback(TypeConstants.DISASSEMBLY_TYPE, callback); 186 } 187 } catch (error) { 188 const errorMessage = error instanceof Error ? error.message : 'Unknown error'; 189 WebSocketManager.getInstance()?.unregisterCallback(TypeConstants.DISASSEMBLY_TYPE, callback); 190 reject(new Error(`Error while processing WebSocket message: ${errorMessage}`)); 191 } 192 }; 193 194 WebSocketManager.getInstance()?.registerCallback(TypeConstants.DISASSEMBLY_TYPE, callback); 195 }), 196 new Promise((_, reject) => setTimeout(() => { 197 WebSocketManager.getInstance()?.unregisterCallback(TypeConstants.DISASSEMBLY_TYPE, callback); 198 reject(new Error('Request timeout, please install the extended service according to the help document')); 199 }, 30000)) 200 ]); 201 } catch (error) { 202 const errorMessage = error instanceof Error ? error.message : 'Unknown error'; 203 this.showError(`Error: can't get assembly instruction because ${errorMessage}, show sample list without assembly instruction`); 204 this.calcutelateErrorShowUpData(); 205 } finally { 206 this.showUpData = [...this.originalShowUpData]; 207 this.assmblerTable!.recycleDataSource = this.showUpData; 208 this.assmblerTable!.reMeauseHeight(); 209 this.hideLoading(); 210 } 211 })(); 212 } 213 214 private calcutelateErrorShowUpData(): void { 215 this.funcSampleMap.forEach((selfCount, offsetToVaddr) => { 216 this.originalShowUpData.push({ 217 selfcount: selfCount, 218 percent: Math.round((selfCount / this.totalCount) * 10000) / 100, 219 // 地址计算也使用 BigInt 220 addr: Number(BigInt.asUintN(64, this.funcBaseAddr + BigInt(offsetToVaddr))), 221 instruction: '', 222 sourceLine: '' 223 }); 224 }); 225 } 226 227 private calculateFuncAsmSapleCount(vaddrList: Array<unknown>): void { 228 vaddrList.forEach(item => { 229 // @ts-ignore 230 const count = this.funcSampleMap.get(item.offsetToVaddr) || 0; 231 // @ts-ignore 232 this.funcSampleMap.set(item.offsetToVaddr, count + 1); 233 }); 234 } 235 236 private formatAsmInstruction(originAsmInstruction: Array<OriginAsmInstruction>): void { 237 this.formattedAsmIntructionArray = originAsmInstruction.map(instructs => ({ 238 selfcount: 0, 239 percent: 0, 240 addr: parseInt(instructs.addr, 16), 241 instruction: instructs.instruction, 242 sourceLine: instructs.sourceLine 243 }) as FormattedAsmInstruction); 244 } 245 246 247 private clearData(): void { 248 this.hideError(); 249 this.funcSampleMap.clear(); 250 this.showUpData = []; 251 this.originalShowUpData = []; 252 this.formattedAsmIntructionArray = []; 253 this.assmblerTable!.recycleDataSource = []; 254 } 255 256 private calcutelateShowUpData(): void { 257 this.funcSampleMap.forEach((selfCount, offsetToVaddr) => { 258 let instructionPosition = offsetToVaddr / 4; 259 this.formattedAsmIntructionArray[instructionPosition].selfcount = selfCount; 260 this.formattedAsmIntructionArray[instructionPosition].percent = Math.round((selfCount / this.totalCount) * 10000) / 100; 261 }); 262 this.originalShowUpData = this.formattedAsmIntructionArray; 263 } 264 265 public connectedCallback(): void { 266 super.connectedCallback(); 267 // 初始化 ResizeObserver 268 this.resizeObserver = new ResizeObserver(() => { 269 if (this.assmblerTable && this.parentElement) { 270 this.assmblerTable.style.height = `${this.parentElement.clientHeight - 50}px`; 271 this.assmblerTable!.reMeauseHeight(); 272 } 273 }); 274 this.resizeObserver.observe(this.parentElement!); 275 } 276 277 public disconnectedCallback(): void { 278 super.disconnectedCallback(); 279 280 // 断开 ResizeObserver 281 if (this.resizeObserver) { 282 this.resizeObserver.disconnect(); 283 this.resizeObserver = null; 284 } 285 } 286}