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'; 17import { type LitTable, TableMode } from '../../../../../base-ui/table/lit-table'; 18import { SelectionParam } from '../../../../bean/BoxSelection'; 19import { type JsCpuProfilerChartFrame, JsCpuProfilerTabStruct } from '../../../../bean/JsStruct'; 20import { procedurePool } from '../../../../database/Procedure'; 21import { findSearchNode, ns2s } from '../../../../database/ui-worker/ProcedureWorkerCommon'; 22import { SpSystemTrace } from '../../../SpSystemTrace'; 23import { TabPaneFilter } from '../TabPaneFilter'; 24import '../TabPaneFilter'; 25import { TabPaneJsCpuHtml } from './TabPaneJsCpu.html'; 26 27export class TabPaneJsCpuCallTree extends BaseElement { 28 protected TYPE_TOP_DOWN = 0; 29 protected TYPE_BOTTOM_UP = 1; 30 private treeTable: HTMLDivElement | undefined | null; 31 private callTreeTable: LitTable | null | undefined; 32 private stackTable: LitTable | null | undefined; 33 private sortKey = ''; 34 private sortType = 0; 35 private callTreeSource: Array<JsCpuProfilerTabStruct> = []; 36 private currentType = 0; 37 private profilerFilter: TabPaneFilter | undefined | null; 38 private searchValue: string = ''; 39 private totalNs: number = 0; 40 private currentSelection: SelectionParam | undefined; 41 private getDataByWorker(args: Array<JsCpuProfilerChartFrame>, handler: Function): void { 42 const key = this.currentType === this.TYPE_TOP_DOWN ? 'jsCpuProfiler-call-tree' : 'jsCpuProfiler-bottom-up'; 43 this.callTreeTable!.mode = TableMode.Retract; 44 procedurePool.submitWithName('logic0', key, args, undefined, (results: Array<JsCpuProfilerTabStruct>) => { 45 handler(results); 46 }); 47 } 48 49 set data(data: SelectionParam | Array<JsCpuProfilerChartFrame>) { 50 if (data instanceof SelectionParam) { 51 if (data === this.currentSelection) { 52 return; 53 } 54 this.currentSelection = data; 55 let chartData; 56 chartData = data.jsCpuProfilerData; 57 this.totalNs = chartData.reduce((acc, struct) => acc + struct.totalTime, 0); 58 if (data.rightNs && data.leftNs) { 59 this.totalNs = Math.min(data.rightNs - data.leftNs, this.totalNs); 60 } 61 62 this.init(); 63 this.getDataByWorker(chartData, (results: Array<JsCpuProfilerTabStruct>): void => { 64 this.setCallTreeTableData(results); 65 }); 66 } 67 } 68 69 protected setCurrentType(type: number): void { 70 this.currentType = type; 71 } 72 73 private init(): void { 74 this.sortKey = ''; 75 this.sortType = 0; 76 this.profilerFilter!.filterValue = ''; 77 const thTable = this.treeTable!.querySelector('.th'); 78 const list = thTable!.querySelectorAll('div'); 79 if (this.treeTable!.hasAttribute('sort')) { 80 this.treeTable!.removeAttribute('sort'); 81 list.forEach((item) => { 82 item.querySelectorAll('svg').forEach((svg): void => { 83 svg.style.display = 'none'; 84 }); 85 }); 86 } 87 } 88 89 private setCallTreeTableData(results: Array<JsCpuProfilerTabStruct>): void { 90 this.clearTab(); 91 const callTreeMap = new Map<number, JsCpuProfilerTabStruct>(); 92 const setTabData = (data: Array<JsCpuProfilerTabStruct>): void => { 93 data.forEach((item) => { 94 if (item.children && item.children.length > 0) { 95 item.children.forEach((it) => { 96 it.parentId = item.id; 97 }); 98 } 99 item.name = SpSystemTrace.DATA_DICT.get(item.nameId) || ''; 100 callTreeMap.set(item.id, item); 101 if (item.scriptName === 'unknown') { 102 item.symbolName = item.name; 103 } else { 104 item.symbolName = `${item.name } ${item.scriptName}`; 105 } 106 item.totalTimePercent = `${((item.totalTime / this.totalNs) * 100).toFixed(1) }%`; 107 item.selfTimePercent = `${((item.selfTime / this.totalNs) * 100).toFixed(1) }%`; 108 item.selfTimeStr = ns2s(item.selfTime); 109 item.totalTimeStr = ns2s(item.totalTime); 110 item.parent = callTreeMap.get(item.parentId!); 111 setTabData(item.children); 112 }); 113 }; 114 setTabData(results); 115 this.callTreeSource = this.sortTree(results); 116 this.callTreeTable!.recycleDataSource = this.callTreeSource; 117 } 118 119 private callTreeRowClickHandler(evt: Event): void { 120 const heaviestStack: JsCpuProfilerTabStruct[] = []; 121 const getHeaviestChildren = (children: Array<JsCpuProfilerTabStruct>): void => { 122 if (children.length === 0) { 123 return; 124 } 125 const heaviestChild = children.reduce( 126 (max, struct): JsCpuProfilerTabStruct => 127 Math.max(max.totalTime, struct.totalTime) === max.totalTime ? max : struct 128 ); 129 heaviestStack?.push(heaviestChild); 130 getHeaviestChildren(heaviestChild.children); 131 }; 132 const getParent = (list: JsCpuProfilerTabStruct): void => { 133 if (list.parent) { 134 heaviestStack.push(list.parent!); 135 getParent(list.parent!); 136 } 137 }; 138 //@ts-ignore 139 const data = evt.detail.data as JsCpuProfilerTabStruct; 140 heaviestStack!.push(data); 141 if (data.parent) { 142 heaviestStack.push(data.parent!); 143 getParent(data.parent!); 144 } 145 heaviestStack.reverse(); 146 getHeaviestChildren(data.children); 147 this.stackTable!.recycleDataSource = heaviestStack; 148 data.isSelected = true; 149 this.stackTable?.clearAllSelection(data); 150 this.stackTable?.setCurrentSelection(data); 151 // @ts-ignore 152 if (evt.detail.callBack) { 153 // @ts-ignore 154 evt.detail.callBack(true); 155 } 156 } 157 158 public initElements(): void { 159 this.callTreeTable = this.shadowRoot?.querySelector('#callTreeTable') as LitTable; 160 this.stackTable = this.shadowRoot?.querySelector('#stackTable') as LitTable; 161 this.treeTable = this.callTreeTable!.shadowRoot?.querySelector('.thead') as HTMLDivElement; 162 this.profilerFilter = this.shadowRoot?.querySelector('#filter') as TabPaneFilter; 163 this.callTreeTable!.addEventListener('row-click', (evt): void => { 164 this.callTreeRowClickHandler(evt); 165 }); 166 this.stackTable!.addEventListener('row-click', (evt) => { 167 //@ts-ignore 168 const data = evt.detail.data as JsCpuProfilerTabStruct; 169 data.isSelected = true; 170 this.callTreeTable!.clearAllSelection(data); 171 this.callTreeTable!.scrollToData(data); 172 // @ts-ignore 173 if (evt.detail.callBack) { 174 // @ts-ignore 175 evt.detail.callBack(true); 176 } 177 }); 178 this.callTreeTable!.addEventListener('column-click', (evt) => { 179 // @ts-ignore 180 this.sortKey = evt.detail.key; 181 // @ts-ignore 182 this.sortType = evt.detail.sort; 183 this.setCallTreeTableData(this.callTreeSource); 184 }); 185 this.profilerFilter!.getFilterData((): void => { 186 if (this.searchValue !== this.profilerFilter!.filterValue) { 187 this.searchValue = this.profilerFilter!.filterValue; 188 findSearchNode(this.callTreeSource, this.searchValue, false); 189 } 190 this.callTreeTable!.setStatus(this.callTreeSource, true); 191 this.setCallTreeTableData(this.callTreeSource); 192 }); 193 } 194 195 public connectedCallback(): void { 196 super.connectedCallback(); 197 new ResizeObserver(() => { 198 // @ts-ignore 199 this.callTreeTable?.shadowRoot.querySelector('.table').style.height = 200 `${this.parentElement!.clientHeight - 32 }px`; 201 this.callTreeTable?.reMeauseHeight(); 202 // @ts-ignore 203 this.stackTable?.shadowRoot.querySelector('.table').style.height = 204 `${this.parentElement!.clientHeight - 32 - 22 }px`; 205 this.stackTable?.reMeauseHeight(); 206 }).observe(this.parentElement!); 207 } 208 209 private sortTree(arr: Array<JsCpuProfilerTabStruct>): Array<JsCpuProfilerTabStruct> { 210 const that = this; 211 function defaultSort(callTreeLeftData: JsCpuProfilerTabStruct, callTreeRightData: JsCpuProfilerTabStruct): number { 212 if (that.currentType === that.TYPE_TOP_DOWN) { 213 return callTreeRightData.totalTime - callTreeLeftData.totalTime; 214 } else { 215 return callTreeRightData.selfTime - callTreeLeftData.selfTime; 216 } 217 } 218 const CallTreeSortArr = arr.sort((callTreeLeftData, callTreeRightData) => { 219 if (this.sortKey === 'selfTimeStr' || this.sortKey === 'selfTimePercent') { 220 if (this.sortType === 0) { 221 return defaultSort(callTreeLeftData, callTreeRightData); 222 } else if (this.sortType === 1) { 223 return callTreeLeftData.selfTime - callTreeRightData.selfTime; 224 } else { 225 return callTreeRightData.selfTime - callTreeLeftData.selfTime; 226 } 227 } else if (this.sortKey === 'symbolName') { 228 if (this.sortType === 0) { 229 return defaultSort(callTreeLeftData, callTreeRightData); 230 } else if (this.sortType === 1) { 231 return (`${callTreeLeftData.symbolName }`).localeCompare(`${callTreeRightData.symbolName }`); 232 } else { 233 return (`${callTreeRightData.symbolName }`).localeCompare(`${callTreeLeftData.symbolName }`); 234 } 235 } else { 236 if (this.sortType === 0) { 237 return defaultSort(callTreeLeftData, callTreeRightData); 238 } else if (this.sortType === 1) { 239 return callTreeLeftData.totalTime - callTreeRightData.totalTime; 240 } else { 241 return callTreeRightData.totalTime - callTreeLeftData.totalTime; 242 } 243 } 244 }); 245 246 CallTreeSortArr.map((call) => { 247 call.children = this.sortTree(call.children); 248 }); 249 return CallTreeSortArr; 250 } 251 252 private clearTab(): void { 253 this.stackTable!.recycleDataSource = []; 254 this.callTreeTable!.recycleDataSource = []; 255 } 256 257 public initHtml(): string { 258 return TabPaneJsCpuHtml; 259 } 260} 261