• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}