• 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 ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseElement } from '../../../../../base-ui/BaseElement.js';
17import { LitTable } from '../../../../../base-ui/table/lit-table.js';
18import { SelectionParam } from '../../../../bean/BoxSelection.js';
19import { JsCpuProfilerChartFrame, JsCpuProfilerTabStruct } from '../../../../bean/JsStruct.js';
20import { procedurePool } from '../../../../database/Procedure.js';
21import { ns2s } from '../../../../database/ui-worker/ProcedureWorkerCommon.js';
22import { FilterData, TabPaneFilter } from '../TabPaneFilter.js';
23import '../TabPaneFilter.js';
24
25export class TabPaneJsCpuCallTree extends BaseElement {
26  protected TYPE_TOP_DOWN = 0;
27  protected TYPE_BOTTOM_UP = 1;
28  private treeTable: HTMLDivElement | undefined | null;
29  private callTreeTable: LitTable | null | undefined;
30  private stackTable: LitTable | null | undefined;
31  private sortKey = '';
32  private sortType = 0;
33  private callTreeSource: Array<JsCpuProfilerTabStruct> = [];
34  private currentType = 0;
35  private profilerFilter: TabPaneFilter | undefined | null;
36  private searchValue: string = '';
37  private totalNs: number = 0;
38  private getDataByWorker(args: Array<JsCpuProfilerChartFrame>, handler: Function): void {
39    const key = this.currentType === this.TYPE_TOP_DOWN ? 'jsCpuProfiler-call-tree' : 'jsCpuProfiler-bottom-up';
40    procedurePool.submitWithName('logic1', key, args, undefined, (results: Array<JsCpuProfilerTabStruct>) => {
41      handler(results);
42    });
43  }
44
45  set data(data: SelectionParam | Array<JsCpuProfilerChartFrame>) {
46    if (data instanceof SelectionParam) {
47      let chartData = [];
48
49      chartData = data.jsCpuProfilerData;
50      this.totalNs = chartData.reduce((acc, struct) => acc + struct.totalTime, 0);
51      if (data.rightNs && data.leftNs) {
52        this.totalNs = Math.min(data.rightNs - data.leftNs, this.totalNs);
53      }
54
55      this.init();
56      this.getDataByWorker(chartData, (results: Array<JsCpuProfilerTabStruct>) => {
57        this.setCallTreeTableData(results);
58      });
59    }
60  }
61
62  protected setCurrentType(type: number) {
63    this.currentType = type;
64  }
65
66  private init() {
67    this.sortKey = '';
68    this.sortType = 0;
69    this.profilerFilter!.filterValue = '';
70    const thTable = this.treeTable!.querySelector('.th');
71    const list = thTable!.querySelectorAll('div');
72    if (this.treeTable!.hasAttribute('sort')) {
73      this.treeTable!.removeAttribute('sort');
74      list.forEach((item) => {
75        item.querySelectorAll('svg').forEach((svg) => {
76          svg.style.display = 'none';
77        });
78      });
79    }
80  }
81
82  private setCallTreeTableData(results: Array<JsCpuProfilerTabStruct>) {
83    this.clearTab();
84    const callTreeMap = new Map<number, JsCpuProfilerTabStruct>();
85    const setTabData = (data: Array<JsCpuProfilerTabStruct>) => {
86      data.forEach((item) => {
87        if (item.children && item.children.length > 0) {
88          item.children.forEach((it) => {
89            it.parentId = item.id;
90          });
91        }
92        callTreeMap.set(item.id, item);
93        if (item.scriptName === 'unknown') {
94          item.symbolName = item.name;
95        } else {
96          item.symbolName = item.name + ` ${item.scriptName}`;
97        }
98        item.totalTimePercent = ((item.totalTime / this.totalNs) * 100).toFixed(1) + '%';
99        item.selfTimePercent = ((item.selfTime / this.totalNs) * 100).toFixed(1) + '%';
100        item.selfTimeStr = ns2s(item.selfTime);
101        item.totalTimeStr = ns2s(item.totalTime);
102        item.parent = callTreeMap.get(item.parentId!);
103        setTabData(item.children);
104      });
105    };
106    setTabData(results);
107    this.callTreeSource = this.sortTree(results);
108    this.callTreeTable!.recycleDataSource = this.callTreeSource;
109  }
110
111  public initElements(): void {
112    this.callTreeTable = this.shadowRoot?.querySelector('#callTreeTable') as LitTable;
113    this.stackTable = this.shadowRoot?.querySelector('#stackTable') as LitTable;
114    this.treeTable = this.callTreeTable!.shadowRoot?.querySelector('.thead') as HTMLDivElement;
115    this.profilerFilter = this.shadowRoot?.querySelector('#filter') as TabPaneFilter;
116    this.callTreeTable!.addEventListener('row-click', (evt) => {
117      const heaviestStack = new Array<JsCpuProfilerTabStruct>();
118
119      const getHeaviestChildren = (children: Array<JsCpuProfilerTabStruct>) => {
120        if (children.length === 0) {
121          return;
122        }
123        const heaviestChild = children.reduce((max, struct) =>
124          Math.max(max.totalTime, struct.totalTime) === max.totalTime ? max : struct
125        );
126        heaviestStack?.push(heaviestChild);
127        getHeaviestChildren(heaviestChild.children);
128      };
129
130      const getParent = (list: JsCpuProfilerTabStruct) => {
131        if (list.parent) {
132          heaviestStack.push(list.parent!);
133          getParent(list.parent!);
134        }
135      };
136
137      //@ts-ignore
138      const data = evt.detail.data as JsCpuProfilerTabStruct;
139      heaviestStack!.push(data);
140      if (data.parent) {
141        heaviestStack.push(data.parent!);
142        getParent(data.parent!);
143      }
144      heaviestStack.reverse();
145      getHeaviestChildren(data.children);
146      this.stackTable!.recycleDataSource = heaviestStack;
147      data.isSelected = true;
148      this.stackTable?.clearAllSelection(data);
149      this.stackTable?.setCurrentSelection(data);
150      // @ts-ignore
151      if (evt.detail.callBack) {
152        // @ts-ignore
153        evt.detail.callBack(true);
154      }
155    });
156
157    this.stackTable!.addEventListener('row-click', (evt) => {
158      //@ts-ignore
159      const data = evt.detail.data as JsCpuProfilerTabStruct;
160      data.isSelected = true;
161      this.callTreeTable!.clearAllSelection(data);
162      this.callTreeTable!.scrollToData(data);
163      // @ts-ignore
164      if (evt.detail.callBack) {
165        // @ts-ignore
166        evt.detail.callBack(true);
167      }
168    });
169    this.callTreeTable!.addEventListener('column-click', (evt) => {
170      // @ts-ignore
171      this.sortKey = evt.detail.key;
172      // @ts-ignore
173      this.sortType = evt.detail.sort;
174      this.setCallTreeTableData(this.callTreeSource);
175    });
176    this.profilerFilter!.getFilterData((data: FilterData) => {
177      if (this.searchValue != this.profilerFilter!.filterValue) {
178        this.searchValue = this.profilerFilter!.filterValue;
179        this.findSearchNode(this.callTreeSource, this.searchValue);
180        this.setCallTreeTableData(this.callTreeSource);
181      }
182    });
183  }
184  private findSearchNode(sampleArray: JsCpuProfilerTabStruct[], search: string) {
185    search = search.toLocaleLowerCase();
186    sampleArray.forEach((sample) => {
187      if (sample.symbolName && sample.symbolName.toLocaleLowerCase().includes(search)) {
188        sample.isSearch = sample.symbolName != undefined && sample.symbolName.toLocaleLowerCase().includes(search);
189      } else {
190        sample.isSearch = false;
191      }
192      if (search === '') {
193        sample.isSearch = false;
194      }
195      if (sample.children.length > 0) {
196        this.findSearchNode(sample.children, search);
197      }
198    });
199  }
200
201  public connectedCallback(): void {
202    super.connectedCallback();
203    new ResizeObserver(() => {
204      // @ts-ignore
205      this.callTreeTable?.shadowRoot.querySelector('.table').style.height = this.parentElement.clientHeight - 32 + 'px';
206      this.callTreeTable?.reMeauseHeight();
207      // @ts-ignore
208      this.stackTable?.shadowRoot.querySelector('.table').style.height =
209        this.parentElement!.clientHeight - 32 - 22 + 'px';
210      this.stackTable?.reMeauseHeight();
211    }).observe(this.parentElement!);
212  }
213
214  private sortTree(arr: Array<JsCpuProfilerTabStruct>): Array<JsCpuProfilerTabStruct> {
215    const that = this;
216    function defaultSort(callTreeLeftData: JsCpuProfilerTabStruct, callTreeRightData: JsCpuProfilerTabStruct) {
217      if (that.currentType === that.TYPE_TOP_DOWN) {
218        return callTreeRightData.totalTime - callTreeLeftData.totalTime;
219      } else {
220        return callTreeRightData.selfTime - callTreeLeftData.selfTime;
221      }
222    }
223    const CallTreeSortArr = arr.sort((callTreeLeftData, callTreeRightData) => {
224      if (this.sortKey === 'selfTimeStr' || this.sortKey === 'selfTimePercent') {
225        if (this.sortType == 0) {
226          return defaultSort(callTreeLeftData, callTreeRightData);
227        } else if (this.sortType == 1) {
228          return callTreeLeftData.selfTime - callTreeRightData.selfTime;
229        } else {
230          return callTreeRightData.selfTime - callTreeLeftData.selfTime;
231        }
232      } else if (this.sortKey === 'symbolName') {
233        if (this.sortType == 0) {
234          return defaultSort(callTreeLeftData, callTreeRightData);
235        } else if (this.sortType == 1) {
236          return (callTreeLeftData.symbolName + '').localeCompare(callTreeRightData.symbolName + '');
237        } else {
238          return (callTreeRightData.symbolName + '').localeCompare(callTreeLeftData.symbolName + '');
239        }
240      } else {
241        if (this.sortType == 0) {
242          return defaultSort(callTreeLeftData, callTreeRightData);
243        } else if (this.sortType == 1) {
244          return callTreeLeftData.totalTime - callTreeRightData.totalTime;
245        } else {
246          return callTreeRightData.totalTime - callTreeLeftData.totalTime;
247        }
248      }
249    });
250
251    CallTreeSortArr.map((call) => {
252      call.children = this.sortTree(call.children);
253    });
254    return CallTreeSortArr;
255  }
256
257  private clearTab(): void {
258    this.stackTable!.recycleDataSource = [];
259    this.callTreeTable!.recycleDataSource = [];
260  }
261
262  public initHtml(): string {
263    return `
264        <style>
265        :host{
266            display: flex;
267            flex-direction: column;
268            padding: 0px 10px 0 10px;
269        }
270        .show{
271            display: flex;
272            flex: 1;
273        }
274        .progress{
275            bottom: 33px;
276            position: absolute;
277            height: 1px;
278            left: 0;
279            right: 0;
280        }
281    </style>
282    <div class="perf-profile-content">
283    <selector id='show_table' class="show">
284        <lit-slicer style="width:100%">
285        <div id="left_table" style="width: 65%">
286            <lit-table id="callTreeTable" style="height: 100%" tree>
287                <lit-table-column width="60%" title="Symbol" data-index="" key="symbolName"  align="flex-start" order></lit-table-column>
288                <lit-table-column width="1fr" title="SelfTime" data-index="selfTimeStr" key="selfTimeStr" align="flex-start"  order></lit-table-column>
289                <lit-table-column width="1fr" title="%" data-index="selfTimePercent" key="selfTimePercent"  align="flex-start"  order></lit-table-column>
290                <lit-table-column width="1fr" title="TotalTime" data-index="totalTimeStr" key="totalTimeStr"  align="flex-start"  order></lit-table-column>
291                <lit-table-column width="1fr" title="%" data-index="totalTimePercent" key="totalTimePercent"  align="flex-start"  order></lit-table-column>
292            </lit-table>
293        </div>
294        <lit-slicer-track ></lit-slicer-track>
295        <div class="right" style="flex: 1;display: flex; flex-direction: row;">
296            <div style="flex: 1;display: block;">
297              <span slot="head" style="height: 22px">Heaviest Stack</span>
298              <lit-table id="stackTable" style="height: auto;">
299                  <lit-table-column width="50%" title="Symbol" data-index="" key="symbolName"  align="flex-start"></lit-table-column>
300                  <lit-table-column width="1fr" title="TotalTime" data-index="totalTimeStr" key="totalTimeStr"  align="flex-start" ></lit-table-column>
301                  <lit-table-column width="1fr" title="%" data-index="totalTimePercent" key="totalTimePercent"  align="flex-start"></lit-table-column>
302              </lit-table>
303          </div>
304        </div>
305        </lit-slicer>
306     </selector>
307     <tab-pane-filter id="filter" input inputLeftText ></tab-pane-filter>
308     <lit-progress-bar class="progress perf-profile-progress"></lit-progress-bar>
309    </div>
310        `;
311  }
312}
313