diff --git a/ide/src/trace/bean/PerfAnalysis.ts b/ide/src/trace/bean/PerfAnalysis.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e2bfd211adb6c475ef4c34e3a83a4cdc79aa409 --- /dev/null +++ b/ide/src/trace/bean/PerfAnalysis.ts @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class PerfFunctionAsmParam { + totalCount: number = 0; + functionName: string = ""; + vaddrList: Array = []; +} + +export class FormattedAsmInstruction { + selfcount: number = 0; + percent: number = 0; + addr: number = 0; + instruction: string = ''; + sourceLine: string = ''; +} + +export class OriginAsmInstruction { + addr: string = ''; + instruction: string = ''; + sourceLine: string = ''; +} + +export class PerfFunctionAsmShowUpData { + addr: number = 0; + instruction: string = ''; + selfCount: number = 0; + percent: number = 0; +} + diff --git a/ide/src/trace/component/trace/base/TraceSheet.ts b/ide/src/trace/component/trace/base/TraceSheet.ts index aa58824fc07ddcdaca52f1ae850d31fa22a928df..9ba7d113303f3a21ef00fe93c27274886782ad03 100644 --- a/ide/src/trace/component/trace/base/TraceSheet.ts +++ b/ide/src/trace/component/trace/base/TraceSheet.ts @@ -95,6 +95,7 @@ import { PerfToolStruct } from '../../../database/ui-worker/ProcedureWorkerPerfT import { GpuCounterStruct } from '../../../database/ui-worker/ProcedureWorkerGpuCounter'; import { TabPaneGpuCounter } from '../sheet/gpu-counter/TabPaneGpuCounter'; import { TabPaneSliceChild } from '../sheet/process/TabPaneSliceChild'; +import { TabPerfFuncAsm } from '../sheet/hiperf/TabPerfFuncAsm'; import { XpowerStatisticStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerStatistic'; import { XpowerAppDetailStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerAppDetail'; import { XpowerWifiStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerWifi'; @@ -103,6 +104,11 @@ import { XpowerThreadInfoStruct } from '../../../database/ui-worker/ProcedureWor import { TabPaneXpowerThreadInfoSelection } from '../sheet/xpower/TabPaneXpowerThreadInfoSelection'; import { TabPaneXpowerGpuFreqSelection } from '../sheet/xpower/TabPaneXpowerGpuFreqSelection'; import { XpowerGpuFreqStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerGpuFreq'; +import { WebSocketManager} from "../../../../webSocket/WebSocketManager"; +import { Constants, TypeConstants} from "../../../../webSocket/Constants"; +import { PerfFunctionAsmParam } from '../../../bean/PerfAnalysis'; +import { info,error } from '../../../../log/Log'; + @element('trace-sheet') export class TraceSheet extends BaseElement { @@ -127,6 +133,7 @@ export class TraceSheet extends BaseElement { private optionsDiv: LitPopover | undefined | null; private optionsSettingTree: LitTree | undefined | null; private tabPaneHeight: string = ''; + private REQ_BUF_SIZE = 1024 * 1024; static get observedAttributes(): string[] { return ['mode']; @@ -215,6 +222,8 @@ export class TraceSheet extends BaseElement { this.perfAnalysisListener(evt); }); // @ts-ignore + this.getComponentByID('box-perf-analysis')?.addFunctionRowClickEventListener(this.functionAnalysisListener.bind(this)); + // @ts-ignore this.getComponentByID('box-native-statistic-analysis')?.addEventListener('row-click', (e: MouseEvent) => { this.nativeAnalysisListener(e); }); @@ -296,6 +305,29 @@ export class TraceSheet extends BaseElement { } } + private functionAnalysisListener(evt: unknown, vaddrList: Array): void { + // @ts-ignore + this.currentPaneID = "box-perf-analysis"; + //隐藏除了当前Tab页的其他Tab页 + this.shadowRoot!.querySelectorAll("lit-tabpane").forEach( + (it): boolean => + it.id !== this.currentPaneID ? (it.hidden = true) : (it.hidden = false) + ); + let asmPane = this.getPaneByID("tab-perf-func-asm"); //通过Id找到需要展示的Tab页 + asmPane.closeable = true; + asmPane.hidden = false; + // @ts-ignore + asmPane.tab = evt.tableName; //设置Tab页标题 + let param = new PerfFunctionAsmParam(); + param.vaddrList = vaddrList; + // @ts-ignore + param.functionName = evt.tableName; + // @ts-ignore + param.totalCount = evt.count; + (asmPane.children.item(0) as TabPerfFuncAsm)!.data = param; + this.litTabs!.activeByKey(asmPane.key); //显示key值(sheetconfig里面对应的index是一个数字)对应的Tab页 + } + private nativeAnalysisListener(e: MouseEvent): void { //@ts-ignore if (e.detail.button === 2 && e.detail.tableName) { @@ -547,7 +579,7 @@ export class TraceSheet extends BaseElement { private importClickEvent(): void { let importFileBt: HTMLInputElement | undefined | null = - this.shadowRoot?.querySelector('#import-file'); + this.shadowRoot?.querySelector('#import-file'); importFileBt!.addEventListener('change', (event): void => { let files = importFileBt?.files; if (files) { @@ -558,30 +590,129 @@ export class TraceSheet extends BaseElement { if (fileList.length > 0) { importFileBt!.disabled = true; window.publish(window.SmartEvent.UI.Loading, { loading: true, text: 'Import So File' }); - threadPool.submit( - 'upload-so', - '', - fileList, - (res: unknown) => { - importFileBt!.disabled = false; // @ts-ignore - if (res.result === 'ok') { - window.publish(window.SmartEvent.UI.UploadSOFile, {}); - } else { - // @ts-ignore - const failedList = res.failedArray.join(','); - window.publish(window.SmartEvent.UI.Error, `parse so file ${failedList} failed!`); - } - }, - 'upload-so' - ); + this.uploadSoOrAN(fileList).then(r => + threadPool.submit( + 'upload-so', + '', + fileList, + (res: unknown) => { + importFileBt!.disabled = false; // @ts-ignore + if (res.result === 'ok') { + window.publish(window.SmartEvent.UI.UploadSOFile, {}); + } else { + // @ts-ignore + const failedList = res.failedArray.join(','); + window.publish(window.SmartEvent.UI.Error, `parse so file ${failedList} failed!`); + } + }, + 'upload-so' + )).finally(() => { + fileList.length = 0; + }) } - fileList.length = 0; } importFileBt!.files = null; importFileBt!.value = ''; }); } + private async uploadSoOrAN(fileList: Array): Promise { + if (fileList) { + fileList.sort((a, b) => { + return b.size - a.size; + }); + await this.uploadAllFiles(fileList); + } + } + + + // 上传文件 + private async uploadAllFiles(fileList: Array): Promise { + // 创建一个副本,避免修改原始的 fileList + const filesToUpload = [...fileList]; + + for (let i = 0; i < filesToUpload.length; i++) { + const file = filesToUpload[i]; + try { + await this.uploadSingleFile(file); + info(`File ${file.name} uploaded successfully.`); + } catch (err) { + error(`Failed to upload file: ${file.name}, error: `, err); + } + } + info(`All files have been uploaded.`); + } + + + private uploadSingleFile = async (file: File | null): Promise => { + if (file) { + let writeSize = 0; + let wsInstance = WebSocketManager.getInstance(); + const fileName = file.name; + let bufferIndex = 0; + + // 定义一个 ACK 回调函数的等待机制 + const waitForAck = (): Promise => { + return new Promise((resolve, reject) => { + wsInstance!.registerCallback(TypeConstants.DISASSEMBLY_TYPE, onAckReceived); + // 定义超时定时器 + const timeout = setTimeout(() => { + // 超时后注销回调并拒绝 Promise + wsInstance!.unregisterCallback(TypeConstants.DISASSEMBLY_TYPE, onAckReceived); + reject(new Error('等待 ACK 超时:文件 ${fileName},索引 ${bufferIndex})')); + }, 10000); + function onAckReceived(cmd: number, result: Uint8Array) { + const decoder = new TextDecoder(); + const jsonString = decoder.decode(result); + let jsonRes = JSON.parse(jsonString); + if (cmd === Constants.DISASSEMBLY_SAVE_BACK_CMD) { + if (jsonRes.fileName === fileName && jsonRes.bufferIndex === bufferIndex) { + wsInstance!.unregisterCallback(TypeConstants.DISASSEMBLY_TYPE, onAckReceived); + clearTimeout(timeout); + if (jsonRes.resultCode === 0) { + bufferIndex++; + // 当收到对应分片的 ACK 时,resolve Promise,继续上传下一个分片 + resolve(); + }else{ + // 上传失败,拒绝 Promise 并返回 + reject(new Error(`Upload failed for file: ${fileName}, index: ${jsonRes.bufferIndex})`)); + } + } + } + } + }); + }; + + while (writeSize < file.size) { + let sliceLen = Math.min(file.size - writeSize, this.REQ_BUF_SIZE); + let blob: Blob | null = file.slice(writeSize, writeSize + sliceLen); + let buffer: ArrayBuffer | null = await blob.arrayBuffer(); + let data: Uint8Array | null = new Uint8Array(buffer); + + const dataObject = { + file_name: fileName, + buffer_index: bufferIndex, + buffer_size: sliceLen, + total_size: file.size, + is_last: writeSize + sliceLen >= file.size, + buffer: Array.from(data), + }; + + + const dataString = JSON.stringify(dataObject); + const textEncoder = new TextEncoder(); + const encodedData = textEncoder.encode(dataString); + wsInstance!.sendMessage(TypeConstants.DISASSEMBLY_TYPE, Constants.DISASSEMBLY_SAVE_CMD, encodedData); + writeSize += sliceLen; + // 等待服务器端确认当前分片的 ACK + await waitForAck(); + data = null; + buffer = null; + blob = null; + } + } + }; + private exportClickEvent(): void { this.exportBt!.onclick = (): void => { let currentTab = this.getTabpaneByKey(this.litTabs?.activekey!); diff --git a/ide/src/trace/component/trace/base/TraceSheetConfig.ts b/ide/src/trace/component/trace/base/TraceSheetConfig.ts index dc0204ebf19468e981293d7df410983b7cb92161..f3f3c35bc6b0f9b68ece9dc95980946fd44bccb8 100644 --- a/ide/src/trace/component/trace/base/TraceSheetConfig.ts +++ b/ide/src/trace/component/trace/base/TraceSheetConfig.ts @@ -151,6 +151,7 @@ import { TabPanePerfAsync } from '../sheet/hiperf/TabPerfAsyncList'; import { TabPaneUserPlugin } from '../sheet/userPlugin/TabPaneUserPlugin'; import { TabPaneDmaFence } from '../sheet/dma-fence/TabPaneDmaFenceSelect'; import { TabPaneSliceChild } from '../sheet/process/TabPaneSliceChild'; +import { TabPerfFuncAsm } from '../sheet/hiperf/TabPerfFuncAsm' export let tabConfig: { [key: string]: { @@ -791,6 +792,10 @@ export let tabConfig: { title: '', type: TabPaneSliceChild, }, + 'tab-perf-func-asm': { + title: '', + type: TabPerfFuncAsm, + }, 'box-xpower-thread-info-selection': { title: 'Thread Info Selection', type: TabPaneXpowerThreadInfoSelection, diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts index e7595dc251ac3c6959e98dd8e71545aeb03983ca..7bfaef4fc04c1ffd3eb5419d1a9a3db12f879c7e 100644 --- a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts @@ -25,6 +25,8 @@ import { LitCheckBox } from '../../../../../base-ui/checkbox/LitCheckBox'; import { initSort } from '../SheetUtils'; import { TabpanePerfProfile } from './TabPerfProfile'; import { TabPanePerfAnalysisHtml } from './TabPanePerfAnalysis.html'; +import { WebSocketManager } from '../../../../../webSocket/WebSocketManager'; +import { Constants, TypeConstants } from '../../../../../webSocket/Constants'; @element('tabpane-perf-analysis') export class TabPanePerfAnalysis extends BaseElement { @@ -65,6 +67,11 @@ export class TabPanePerfAnalysis extends BaseElement { private isComplete: boolean = true; private currentSelectionParam: SelectionParam | undefined | null; static tabLoadingList: Array = []; + private vaddrList: Array = []; + private selectedTabfileName: string = ''; + private clickFuncVaddrList: Array = []; + private functionListener!: Function | undefined | null; + private currentSoName: string = ''; set data(val: SelectionParam) { if (val === this.currentSelection) { @@ -204,6 +211,7 @@ export class TabPanePerfAnalysis extends BaseElement { this.addRowClickEventListener(this.perfTableProcess!, this.perfProcessLevelClickEvent.bind(this)); this.addRowClickEventListener(this.perfTableThread!, this.perfThreadLevelClickEvent.bind(this)); this.addRowClickEventListener(this.perfTableSo!, this.perfSoLevelClickEvent.bind(this)); + this.addRowClickEventListener(this.tableFunction!, this.functionClickEvent.bind(this)); } private addRowClickEventListener(table: LitTable, clickEvent: Function): void { @@ -544,6 +552,8 @@ export class TabPanePerfAnalysis extends BaseElement { private perfSoLevelClickEvent(it: unknown): void { this.reset(this.tableFunction!, true); this.showAssignLevel(this.tableFunction!, this.perfTableSo!, 3, this.functionData); + // @ts-ignore + this.currentSoName = it.tableName; this.getHiperfFunction(it); let title = ''; if (this.processName.length > 0) { @@ -559,6 +569,33 @@ export class TabPanePerfAnalysis extends BaseElement { } this.titleEl!.textContent = title; this.perfAnalysisPie?.hideTip(); + // @ts-ignore + this.selectedTabfileName = it.tableName; + } + + private functionClickEvent(it: unknown) { + this.clickFuncVaddrList = this.vaddrList.filter((item: unknown) => { + // @ts-ignore + return item.process_id === it.pid && + // @ts-ignore + item.thread_id === it.tid && + // @ts-ignore + item.libName === this.selectedTabfileName && + // @ts-ignore + item.symbolName === it.tableName + }) + if (this.clickFuncVaddrList.length > 0) { + const textEncoder = new TextEncoder(); + const queryData = { + elf_name: this.currentSoName, //@ts-ignore + vaddr: this.clickFuncVaddrList[0].vaddrInFile, //@ts-ignore + func: it.tableName + }; + const dataString = JSON.stringify(queryData); + const encodedData = textEncoder.encode(dataString); + WebSocketManager.getInstance()?.sendMessage(TypeConstants.DISASSEMBLY_TYPE, Constants.DISASSEMBLY_QUERY_CMD, encodedData); + } + this.functionListener!(it, this.clickFuncVaddrList); } private sortByColumn(): void { @@ -1117,6 +1154,15 @@ export class TabPanePerfAnalysis extends BaseElement { TabPanePerfAnalysis.tabLoadingList.shift(); } }); + const args = [ + { + funcName: 'getVaddrToFile', + funcArgs: [val], + }, + ]; + procedurePool.submitWithName('logic0', 'perf-vaddr', args, undefined, (results: Array) => { + this.vaddrList = results; + }) } private getDataByWorker(val: SelectionParam, handler: Function): void { @@ -1154,6 +1200,10 @@ export class TabPanePerfAnalysis extends BaseElement { }).observe(this.parentElement!); } + public addFunctionRowClickEventListener(clickEvent: Function): void { + this.functionListener = clickEvent; + } + initHtml(): string { return TabPanePerfAnalysisHtml; } diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.html.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.html.ts new file mode 100644 index 0000000000000000000000000000000000000000..70f116e9a9635de4a9e869d2ffb568e55ac7f1fb --- /dev/null +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.html.ts @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const TabPerfFuncAsmHtml = ` + +
+
+
+
+
.text section:
+
Total Count:
+
+
+
+ + + + + + + + +
+
+`; diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.ts new file mode 100644 index 0000000000000000000000000000000000000000..69e30cb49e07927c68eb482283437fb01a3c8aa6 --- /dev/null +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.ts @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF unknown KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TabPerfFuncAsmHtml } from "./TabPerfFuncAsm.html"; +import { BaseElement, element } from "../../../../../base-ui/BaseElement"; +import { LitTable } from "../../../../../base-ui/table/lit-table"; +import { + FormattedAsmInstruction, + PerfFunctionAsmParam, + OriginAsmInstruction, +} from "../../../../bean/PerfAnalysis"; +import { WebSocketManager } from "../../../../../webSocket/WebSocketManager"; +import { Constants, TypeConstants } from "../../../../../webSocket/Constants"; + +@element("tab-perf-func-asm") +export class TabPerfFuncAsm extends BaseElement { + private assmblerTable: LitTable | null | undefined; + private loadingElement: HTMLElement | null | undefined; + private functionName: string = ""; + private totalCount: number = 0; + private totalCountElement: HTMLDivElement | null | undefined; + private textFileOffElement: HTMLDivElement | null | undefined; + private errorMessageElement: HTMLDivElement | null | undefined; + private funcBaseAddr: bigint = BigInt(0); + // Key: offset; Value: selfcount + private funcSampleMap: Map = new Map(); + private showUpData: FormattedAsmInstruction[] = []; + private originalShowUpData: FormattedAsmInstruction[] = []; + private formattedAsmIntructionArray: FormattedAsmInstruction[] = []; + private resizeObserver: ResizeObserver | null = null; + + initHtml(): string { + return TabPerfFuncAsmHtml; + } + + initElements(): void { + this.assmblerTable = this.shadowRoot!.querySelector( + "#perf-function-asm-table" + ); + this.loadingElement = + this.shadowRoot!.querySelector("#loading"); + this.totalCountElement = + this.shadowRoot!.querySelector("#total-count"); + this.textFileOffElement = + this.shadowRoot!.querySelector("#text-file-off"); + this.errorMessageElement = this.shadowRoot!.querySelector("#error-message"); + + this.assmblerTable!.style.display = "grid"; + + this.assmblerTable!.itemTextHandleMap.set("addr", (value: unknown) => { + return `0x${(value as number).toString(16)}`; + }); + + this.assmblerTable!.itemTextHandleMap.set("selfcount", (value: unknown) => { + return (value as number) === 0 ? "" : (value as number).toString(); + }); + + this.assmblerTable!.itemTextHandleMap.set("percent", (value: unknown) => { + return (value as number) === 0 ? "" : (value as number).toString(); + }); + + this.assmblerTable!.itemTextHandleMap.set("instruction", (value: unknown) => { + return (value as string) === "" ? "INVALID" : (value as string); + }); + + this.assmblerTable!.itemTextHandleMap.set("sourceLine", (value: unknown) => { + return (value as string) || ""; + }); + + this.assmblerTable!.addEventListener("column-click", ((evt: Event) => { + const {key, sort} = (evt as CustomEvent).detail; + if (key === "selfcount") { + if (sort === 0) { + this.assmblerTable!.recycleDataSource = this.originalShowUpData; + this.assmblerTable!.reMeauseHeight(); + } else { + this.showUpData.sort((a, b) => { + return sort === 1 + ? a.selfcount - b.selfcount + : b.selfcount - a.selfcount; + }); + this.assmblerTable!.recycleDataSource = this.showUpData; + this.assmblerTable!.reMeauseHeight(); + } + } else if (key === "percent") { + if (sort === 0) { + this.assmblerTable!.recycleDataSource = this.originalShowUpData; + this.assmblerTable!.reMeauseHeight(); + } else { + this.showUpData.sort((a, b) => { + return sort === 1 ? a.percent - b.percent : b.percent - a.percent; + }); + this.assmblerTable!.recycleDataSource = this.showUpData; + this.assmblerTable!.reMeauseHeight(); + } + } + }) as EventListener); + } + + private updateTotalCount(): void { + if (this.functionName) { + this.totalCountElement!.innerHTML = `Total Count: ${this.totalCount}`; + } + } + + private showLoading(): void { + if (this.loadingElement) { + this.loadingElement.removeAttribute("hidden"); + } + } + + private hideLoading(): void { + if (this.loadingElement) { + this.loadingElement.setAttribute("hidden", ""); + } + } + + private showError(message: string): void { + if (this.errorMessageElement) { + this.errorMessageElement.textContent = message; + this.errorMessageElement.style.display = 'block'; + } + } + + private hideError(): void { + if (this.errorMessageElement) { + this.errorMessageElement.style.display = 'none'; + } + } + + set data(data: PerfFunctionAsmParam) { + if (this.functionName === data.functionName || data.functionName === undefined) { + return; + } + + (async () => { + try { + this.clearData(); + this.functionName = data.functionName; + this.totalCount = data.totalCount; + this.updateTotalCount(); + this.showLoading(); + // @ts-ignore + const vaddrInFile = data.vaddrList[0].vaddrInFile; + // 1. 先转成 BigInt + // 2. 用 asUintN 转成无符号64位 + // 3. 如果需要用作数值运算,再转回 Number + this.funcBaseAddr = BigInt.asUintN(64, BigInt(vaddrInFile)); + // 1. 计算采样数据 + this.calculateFuncAsmSapleCount(data.vaddrList); + // 2. 等待汇编指令数据 + let callback: (cmd: number, e: Uint8Array) => void; + + await Promise.race([ + new Promise((resolve, reject) => { + callback = (cmd: number, e: Uint8Array) => { + try { + + if (cmd === Constants.DISASSEMBLY_QUERY_BACK_CMD) { + const result = JSON.parse(new TextDecoder().decode(e)); + if (result.resultCode === 0) { + if (result.anFileOff) { + this.textFileOffElement!.innerHTML = `.text section: ${result.anFileOff}`; + this.textFileOffElement!.style.display = 'block'; + } else { + this.textFileOffElement!.style.display = 'none'; + } + this.formatAsmInstruction(JSON.parse(result.resultMessage)); + this.calcutelateShowUpData(); + resolve(); + } else { + reject(new Error(`Failed with code: ${result.resultCode}, error message: ${result.resultMessage}`)); + } + WebSocketManager.getInstance()?.unregisterCallback(TypeConstants.DISASSEMBLY_TYPE, callback); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + WebSocketManager.getInstance()?.unregisterCallback(TypeConstants.DISASSEMBLY_TYPE, callback); + reject(new Error(`Error while processing WebSocket message: ${errorMessage}`)); + } + }; + + WebSocketManager.getInstance()?.registerCallback(TypeConstants.DISASSEMBLY_TYPE, callback); + }), + new Promise((_, reject) => setTimeout(() => { + WebSocketManager.getInstance()?.unregisterCallback(TypeConstants.DISASSEMBLY_TYPE, callback); + reject(new Error('Request timeout, please install the extended service according to the help document')); + }, 30000)) + ]); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + this.showError(`Error: can't get assembly instruction because ${errorMessage}, show sample list without assembly instruction`); + this.calcutelateErrorShowUpData(); + } finally { + this.showUpData = [...this.originalShowUpData]; + this.assmblerTable!.recycleDataSource = this.showUpData; + this.assmblerTable!.reMeauseHeight(); + this.hideLoading(); + } + })(); + } + + private calcutelateErrorShowUpData(): void { + this.funcSampleMap.forEach((selfCount, offsetToVaddr) => { + this.originalShowUpData.push({ + selfcount: selfCount, + percent: Math.round((selfCount / this.totalCount) * 10000) / 100, + // 地址计算也使用 BigInt + addr: Number(BigInt.asUintN(64, this.funcBaseAddr + BigInt(offsetToVaddr))), + instruction: '', + sourceLine: '' + }) + }) + } + + private calculateFuncAsmSapleCount(vaddrList: Array): void { + vaddrList.forEach(item => { + // @ts-ignore + const count = this.funcSampleMap.get(item.offsetToVaddr) || 0; + // @ts-ignore + this.funcSampleMap.set(item.offsetToVaddr, count + 1); + }); + } + + private formatAsmInstruction(originAsmInstruction: Array) { + this.formattedAsmIntructionArray = originAsmInstruction.map(instructs => ({ + selfcount: 0, + percent: 0, + addr: parseInt(instructs.addr, 16), + instruction: instructs.instruction, + sourceLine: instructs.sourceLine + }) as FormattedAsmInstruction); + } + + + private clearData(): void { + this.hideError(); + this.funcSampleMap.clear(); + this.showUpData = []; + this.originalShowUpData = []; + this.formattedAsmIntructionArray = []; + this.assmblerTable!.recycleDataSource = []; + } + + private calcutelateShowUpData(): void { + this.funcSampleMap.forEach((selfCount, offsetToVaddr) => { + let instructionPosition = offsetToVaddr / 4; + this.formattedAsmIntructionArray[instructionPosition].selfcount = selfCount; + this.formattedAsmIntructionArray[instructionPosition].percent = Math.round((selfCount / this.totalCount) * 10000) / 100; + }) + this.originalShowUpData = this.formattedAsmIntructionArray; + } + + public connectedCallback(): void { + super.connectedCallback(); + // 初始化 ResizeObserver + this.resizeObserver = new ResizeObserver(() => { + if (this.assmblerTable && this.parentElement) { + this.assmblerTable.style.height = `${this.parentElement.clientHeight - 50}px`; + this.assmblerTable!.reMeauseHeight(); + } + }); + this.resizeObserver.observe(this.parentElement!); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + + // 断开 ResizeObserver + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + } +} \ No newline at end of file diff --git a/ide/src/trace/database/SqlLiteWorker.ts b/ide/src/trace/database/SqlLiteWorker.ts index 29506f47a5c49fee11f53e5b041f5eb2fa9abdcc..673cc414fca05e3f41ffd2e1d22f5d43a2ed3173 100644 --- a/ide/src/trace/database/SqlLiteWorker.ts +++ b/ide/src/trace/database/SqlLiteWorker.ts @@ -13,6 +13,10 @@ * limitations under the License. */ +importScripts('sql-wasm.js'); +// @ts-ignore +import {WebSocketManager} from "../../webSocket/WebSocketManager"; + importScripts('sql-wasm.js'); // @ts-ignore import { temp_init_sql_list } from './TempSql'; @@ -20,7 +24,11 @@ import { execProtoForWorker } from './data-trafic/utils/ExecProtoForWorker'; import { TraficEnum } from './data-trafic/utils/QueryEnum'; let conn: unknown = null; - +let enc = new TextEncoder(); +let dec = new TextDecoder(); +const REQ_BUF_SIZE = 4 * 1024 * 1024; +let uploadSoActionId: string = ''; +const failedArray: Array = []; self.onerror = function (error): void { }; self.onmessage = async (e: unknown): Promise => { @@ -97,5 +105,19 @@ self.onmessage = async (e: unknown): Promise => { return []; } }); + } else if (action === 'upload-so') { + onmessageByUploadSoAction(e); } }; + +function onmessageByUploadSoAction(e: unknown): void { + // @ts-ignore + uploadSoActionId = e.data.id; + // @ts-ignore + const result = 'ok'; + self.postMessage({ + id: uploadSoActionId, + action: 'upload-so', + results: { result: result, failedArray: failedArray }, + }); +} \ No newline at end of file diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts index 4435021398a51697868e81120b7318764375bd60..2f63034e2f72adebf39d074cce1a84d7ed4e90f3 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts @@ -111,6 +111,12 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { case 'perf-action': this.perfAction(data); break; + case 'perf-vaddr-back': + this.rebackVaddrList(data); + break; + case 'perf-vaddr': + this.perfGetVaddr(data); + break; case 'perf-reset': this.perfReset(); break; @@ -283,6 +289,43 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { this.isAnalysis = false; } } + + rebackVaddrList(data: unknown) { + // @ts-ignore + let vaddrCallchainList = convertJSON(data.params.list); + let sampleCallChainList: unknown = []; + for (let i = 0; i < vaddrCallchainList.length; i++) { + let funcVaddrLastItem = {}; + // @ts-ignore + let callChains = [...this.callChainData[vaddrCallchainList[i].callchain_id]]; + const lastCallChain = callChains[callChains.length - 1]; + // @ts-ignore + funcVaddrLastItem.callchain_id = lastCallChain.sampleId; + // @ts-ignore + funcVaddrLastItem.symbolName = this.dataCache.dataDict.get(lastCallChain.name as number); + // @ts-ignore + funcVaddrLastItem.vaddrInFile = lastCallChain.vaddrInFile; + // @ts-ignore + funcVaddrLastItem.offsetToVaddr = lastCallChain.offsetToVaddr; + // @ts-ignore + funcVaddrLastItem.process_id = vaddrCallchainList[i].process_id; + // @ts-ignore + funcVaddrLastItem.thread_id = vaddrCallchainList[i].thread_id; + // @ts-ignore + funcVaddrLastItem.libName = lastCallChain.fileName; + // @ts-ignore + sampleCallChainList.push(funcVaddrLastItem); + } + + self.postMessage({ + //@ts-ignore + id: data.id, + //@ts-ignore + action: data.action, + results: sampleCallChainList, + }); + } + private perfAction(data: unknown): void { //@ts-ignore const params = data.params; @@ -312,6 +355,17 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { } } + private perfGetVaddr(data: unknown) { + // @ts-ignore + const params = data.params; + this.backVaddrData(data); + } + + backVaddrData(data: unknown) { + // @ts-ignore + this.handleDataByFuncName(data.params[0].funcName, data.params[0].funcArgs); + } + private perfReset(): void { this.isHideThread = false; this.isHideThreadState = false; @@ -461,6 +515,7 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { `select c.name, c.callchain_id as sampleId, c.vaddr_in_file as vaddrInFile, + c.offset_to_vaddr as offsetToVaddr, c.file_id as fileId, c.depth, c.symbol_id as symbolId, @@ -1157,6 +1212,50 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { } } + private queryVaddrToFile(funcArgs: unknown[]): void { + if (funcArgs[1]) { + let sql = ''; + //@ts-ignore + if (funcArgs[1].processId !== undefined) { + //@ts-ignore + sql += `and thread.process_id = ${funcArgs[1].processId}`; + } + //@ts-ignore + if (funcArgs[1].threadId !== undefined) { + //@ts-ignore + sql += ` and s.thread_id = ${funcArgs[1].threadId}`; + } + //@ts-ignore + this.getVaddrToFile(funcArgs[0], sql); + } else { + //@ts-ignore + this.getVaddrToFile(funcArgs[0]); + } + } + + private getVaddrToFile(selectionParam: SelectionParam, sql?: string): void { + let filterSql = this.setFilterSql(selectionParam, sql); + this.queryData( + this.currentEventId, + 'perf-vaddr-back', + `select s.callchain_id, + s.thread_id, + thread.process_id + from perf_sample s, trace_range t + left join perf_thread thread on s.thread_id = thread.thread_id + where timestamp_trace between ${selectionParam.leftNs} + t.start_ts + and ${selectionParam.rightNs} + t.start_ts + and s.callchain_id != -1 + and s.thread_id != 0 ${filterSql} + group by s.callchain_id`, + { + $startTime: selectionParam.leftNs, + $endTime: selectionParam.rightNs, + $sql: filterSql, + } + ); + } + private handleDataByFuncName(funcName: string, funcArgs: unknown[]): unknown { let result; switch (funcName) { @@ -1171,6 +1270,8 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { break; case 'getCurrentDataFromDbBottomUp': this.queryDataFromDb(funcArgs, 'perf-bottomUp'); + case 'getVaddrToFile': + this.queryVaddrToFile(funcArgs); break; case 'hideSystemLibrary': this.hideSystemLibrary(); @@ -1206,7 +1307,7 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { case 'setSearchValue': this.searchValue = funcArgs[0] as string; break; - + case 'combineAnalysisCallChain': result = this.combineCallChainForAnalysis(); break; @@ -1237,6 +1338,9 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { ) { let analysisSample = new PerfAnalysisSample( threadName, + lastCallChain.depth, + lastCallChain.vaddrInFile, + lastCallChain.offsetToVaddr, processName, lastCallChain.fileId, lastCallChain.fileName, @@ -1426,6 +1530,7 @@ export class PerfCallChain { sampleId: number = 0; callChainId: number = 0; vaddrInFile: number = 0; + offsetToVaddr: number = 0; tid: number = 0; pid: number = 0; name: number | string = 0; @@ -1499,6 +1604,7 @@ export class PerfCallChainMerageData extends ChartStruct { initChildren: PerfCallChainMerageData[] = []; type: number = 0; vaddrInFile: number = 0; + offsetToVaddr: number = 0; isSelected: boolean = false; searchShow: boolean = true; isSearch: boolean = false; @@ -1551,6 +1657,7 @@ export class PerfCallChainMerageData extends ChartStruct { currentNode.tid = sample.tid; currentNode.libName = callChain.fileName; currentNode.vaddrInFile = callChain.vaddrInFile; + currentNode.offsetToVaddr = callChain.offsetToVaddr; currentNode.lib = callChain.fileName; currentNode.addr = `${'0x'}${callChain.vaddrInFile.toString(16)}`; currentNode.canCharge = callChain.canCharge; @@ -1605,6 +1712,9 @@ export class PerfCmdLine { class PerfAnalysisSample extends PerfCountSample { threadName: string; + depth: number; + vaddr_in_file: number; + offset_to_vaddr: number; processName: string; libId: number; libName: string; @@ -1613,6 +1723,9 @@ class PerfAnalysisSample extends PerfCountSample { constructor( threadName: string, + depth: number, + vaddr_in_file: number, + offset_to_vaddr: number, processName: string, libId: number, libName: string, @@ -1621,6 +1734,9 @@ class PerfAnalysisSample extends PerfCountSample { ) { super(); this.threadName = threadName; + this.depth = depth; + this.vaddr_in_file = vaddr_in_file; + this.offset_to_vaddr = offset_to_vaddr; this.processName = processName; this.libId = libId; this.libName = libName; diff --git a/ide/src/webSocket/Constants.ts b/ide/src/webSocket/Constants.ts index 91fb9c546aa9809c04e011b0528e63c46a114dbd..c44949c0856bd32f0a7f4e1d3428c097ff950abe 100644 --- a/ide/src/webSocket/Constants.ts +++ b/ide/src/webSocket/Constants.ts @@ -23,6 +23,10 @@ export class Constants { static GET_VERSION_CMD = 1; static UPDATE_SUCCESS_CMD = 2; // 升级成功 static UPDATE_FAIL_CMD = 4; // 升级失败 + static DISASSEMBLY_SAVE_CMD = 1; + static DISASSEMBLY_SAVE_BACK_CMD = 2; + static DISASSEMBLY_QUERY_CMD = 3; + static DISASSEMBLY_QUERY_BACK_CMD = 4; } export class TypeConstants { @@ -32,6 +36,7 @@ export class TypeConstants { static DIAGNOSIS_TYPE = 8; static SENDDB_CMD = 1; static DIAGNOSIS_CMD = 3; + static DISASSEMBLY_TYPE = 12; static ARKTS_TYPE = 9; static PROCESS_TYPE = 3; static USB_TYPE = 10; diff --git a/ide/src/webSocket/WebSocketManager.ts b/ide/src/webSocket/WebSocketManager.ts index 27861b9230f530bc890e7a9afb996160143b89b5..c01c5a4fef46de9bdd0882e4b5e5d153eea81688 100644 --- a/ide/src/webSocket/WebSocketManager.ts +++ b/ide/src/webSocket/WebSocketManager.ts @@ -34,7 +34,7 @@ export class WebSocketManager { static instance: WebSocketManager | null | undefined = null; url: string = `ws://localhost:${Constants.NODE_PORT}`; private websocket: WebSocket | null | undefined = null; - private distributeMap: Map = new Map(); + private distributeMap: Map = new Map(); private sessionId: number | null | undefined; private session: bigint | null | undefined; private heartbeatInterval: number | null | undefined; @@ -56,7 +56,7 @@ export class WebSocketManager { this.websocket.binaryType = 'arraybuffer'; this.websocket.onopen = (): void => { this.status = GetStatuses.CONNECTED; - // 设置心跳定时器 + // 设置心跳定时器 this.sendHeartbeat(); // 连接后登录 this.login(); @@ -99,7 +99,7 @@ export class WebSocketManager { this.businessMessage(decode); } } - + // 登录 loginMessage(decode: MessageParam): void { if (decode.cmd === Constants.LOGIN_CMD) { @@ -133,13 +133,19 @@ export class WebSocketManager { this.finalStatus(); } else if (decode.cmd === Constants.UPDATE_FAIL_CMD) { // 升级失败 this.status = GetStatuses.UPGRADEFAILED; - this.finalStatus(); + this.finalStatus(); } } // 业务 businessMessage(decode: MessageParam): void { - this.distributeMap.get(decode.type!)?.messageCallback(decode.cmd, decode.data); + if (this.distributeMap.has(decode.type!)){ + const callbackObj = this.distributeMap.get(decode.type!)!; + // 遍历调用所有 eventCallBacks + callbackObj.messageCallbacks.forEach(callback => { + callback(decode.cmd, decode.data); + }); + } } // get版本 @@ -207,8 +213,45 @@ export class WebSocketManager { * 模块调用 */ registerMessageListener(type: number, callback: Function, eventCallBack: Function): void { + this.register(type, callback, eventCallBack); + } + + /** + * 消息监听器 + * listener是不同模块传来接收数据的函数 + * 模块调用 + */ + registerCallback(type: number, callback: Function): void { + this.register(type, callback); + } + + private register(type: number, callback: Function, eventCallBack: Function = () => {}): void { + let callbackObj = this.distributeMap.get(type); + if (!callbackObj) { + callbackObj = { + messageCallbacks: [callback], + eventCallBack: eventCallBack + }; + this.distributeMap.set(type, callbackObj); + } else { + if (!callbackObj.messageCallbacks.includes(callback)) { + callbackObj.messageCallbacks.push(callback); + } + callbackObj.eventCallBack = eventCallBack; + } + } + + // 删除回调函数 + unregisterCallback(type: number, callback: Function): void { if (!this.distributeMap.has(type)) { - this.distributeMap.set(type, { 'messageCallback': callback, 'eventCallBack': eventCallBack }); + return; + } + const callbackObj = this.distributeMap.get(type)!; + callbackObj.messageCallbacks = callbackObj.messageCallbacks.filter((cb) => cb !== callback); + + // 如果回调数组为空,同时 eventCallBack 也为空,则可以删除整个类型 + if (callbackObj.messageCallbacks.length === 0 && !callbackObj.eventCallBack) { + this.distributeMap.delete(type); } } @@ -242,7 +285,7 @@ export class WebSocketManager { this.websocket!.send(encode!); } - // 定时检查心跳 + // 定时检查心跳 sendHeartbeat(): void { this.heartbeatInterval = window.setInterval(() => { if (this.status === GetStatuses.READY) { @@ -280,16 +323,20 @@ export class WebSocketManager { obj.data = data; } } - + // 检查状态 中间状态,最终失败状态,最终成功状态 - checkStatus(reconnect: number): void { + checkStatus(type: number): void { // @ts-ignore let statuses = this.getStatusesPrompt()[this.status]; - if (statuses.type === INTERMEDIATE_STATE) { - this.distributeMap.get(reconnect)!.eventCallBack(this.status); - } else if (statuses.type === FAILED_STATE) { - this.reconnect = reconnect; - this.connectWebSocket(); + const distributeEntry = this.distributeMap.get(type); + + if (distributeEntry && typeof distributeEntry.eventCallBack === 'function') { + if (statuses.type === INTERMEDIATE_STATE) { + distributeEntry.eventCallBack(this.status); + } else if (statuses.type === FAILED_STATE) { + this.reconnect = type; + this.connectWebSocket(); + } } }