• 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 { 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}