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