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.stackTable!.recycleDataSource = []; 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 item.scriptName === 'unknown' 102 ? (item.symbolName = item.name) 103 : (item.symbolName = `${item.name} ${item.scriptName}`); 104 item.totalTimePercent = `${((item.totalTime / this.totalNs) * 100).toFixed(1)}%`; 105 item.selfTimePercent = `${((item.selfTime / this.totalNs) * 100).toFixed(1)}%`; 106 item.selfTimeStr = ns2s(item.selfTime); 107 item.totalTimeStr = ns2s(item.totalTime); 108 item.parent = callTreeMap.get(item.parentId!); 109 setTabData(item.children); 110 }); 111 }; 112 setTabData(results); 113 this.callTreeSource = this.sortTree(results); 114 this.callTreeTable!.recycleDataSource = this.callTreeSource; 115 } 116 117 private callTreeRowClickHandler(evt: Event): void { 118 const heaviestStack: JsCpuProfilerTabStruct[] = []; 119 const getHeaviestChildren = (children: Array<JsCpuProfilerTabStruct>): void => { 120 if (children.length === 0) { 121 return; 122 } 123 const heaviestChild = children.reduce( 124 (max, struct): JsCpuProfilerTabStruct => 125 Math.max(max.totalTime, struct.totalTime) === max.totalTime ? max : struct 126 ); 127 heaviestStack?.push(heaviestChild); 128 getHeaviestChildren(heaviestChild.children); 129 }; 130 const getParent = (list: JsCpuProfilerTabStruct): void => { 131 if (list.parent) { 132 heaviestStack.push(list.parent!); 133 getParent(list.parent!); 134 } 135 }; 136 //@ts-ignore 137 const data = evt.detail.data as JsCpuProfilerTabStruct; 138 heaviestStack!.push(data); 139 if (data.parent) { 140 heaviestStack.push(data.parent!); 141 getParent(data.parent!); 142 } 143 heaviestStack.reverse(); 144 getHeaviestChildren(data.children); 145 this.stackTable!.recycleDataSource = heaviestStack; 146 data.isSelected = true; 147 this.stackTable?.clearAllSelection(data); 148 this.stackTable?.setCurrentSelection(data); 149 // @ts-ignore 150 if (evt.detail.callBack) { 151 // @ts-ignore 152 evt.detail.callBack(true); 153 } 154 } 155 156 public initElements(): void { 157 this.callTreeTable = this.shadowRoot?.querySelector('#callTreeTable') as LitTable; 158 this.stackTable = this.shadowRoot?.querySelector('#stackTable') as LitTable; 159 this.treeTable = this.callTreeTable!.shadowRoot?.querySelector('.thead') as HTMLDivElement; 160 this.profilerFilter = this.shadowRoot?.querySelector('#filter') as TabPaneFilter; 161 this.callTreeTable!.addEventListener('row-click', (evt): void => { 162 this.callTreeRowClickHandler(evt); 163 }); 164 this.stackTable!.addEventListener('row-click', (evt) => { 165 //@ts-ignore 166 const data = evt.detail.data as JsCpuProfilerTabStruct; 167 data.isSelected = true; 168 this.callTreeTable!.clearAllSelection(data); 169 this.callTreeTable!.scrollToData(data); 170 // @ts-ignore 171 if (evt.detail.callBack) { 172 // @ts-ignore 173 evt.detail.callBack(true); 174 } 175 }); 176 this.callTreeTable!.addEventListener('column-click', (evt) => { 177 // @ts-ignore 178 this.sortKey = evt.detail.key; 179 // @ts-ignore 180 this.sortType = evt.detail.sort; 181 this.setCallTreeTableData(this.callTreeSource); 182 }); 183 this.profilerFilter!.getFilterData((): void => { 184 if (this.searchValue !== this.profilerFilter!.filterValue) { 185 this.searchValue = this.profilerFilter!.filterValue; 186 findSearchNode(this.callTreeSource, this.searchValue, false); 187 } 188 this.callTreeTable!.setStatus(this.callTreeSource, true); 189 this.setCallTreeTableData(this.callTreeSource); 190 }); 191 } 192 193 public connectedCallback(): void { 194 super.connectedCallback(); 195 new ResizeObserver(() => { 196 // @ts-ignore 197 this.callTreeTable?.shadowRoot.querySelector('.table').style.height = `${ 198 this.parentElement!.clientHeight - 32 199 }px`; 200 this.callTreeTable?.reMeauseHeight(); 201 // @ts-ignore 202 this.stackTable?.shadowRoot.querySelector('.table').style.height = `${ 203 this.parentElement!.clientHeight - 32 - 22 204 }px`; 205 this.stackTable?.reMeauseHeight(); 206 }).observe(this.parentElement!); 207 } 208 209 private sortTree(arr: Array<JsCpuProfilerTabStruct>): Array<JsCpuProfilerTabStruct> { 210 const defaultSort = (callTreeLeftData: JsCpuProfilerTabStruct, callTreeRightData: JsCpuProfilerTabStruct): number => { 211 if (this.currentType === this.TYPE_TOP_DOWN) { 212 return callTreeRightData.totalTime - callTreeLeftData.totalTime; 213 } else { 214 return callTreeRightData.selfTime - callTreeLeftData.selfTime; 215 } 216 }; 217 const CallTreeSortArr = arr.sort((callTreeLeftData, callTreeRightData) => { 218 if (this.sortKey === 'selfTimeStr' || this.sortKey === 'selfTimePercent') { 219 if (this.sortType === 0) { 220 return defaultSort(callTreeLeftData, callTreeRightData); 221 } else if (this.sortType === 1) { 222 return callTreeLeftData.selfTime - callTreeRightData.selfTime; 223 } else { 224 return callTreeRightData.selfTime - callTreeLeftData.selfTime; 225 } 226 } else if (this.sortKey === 'symbolName') { 227 if (this.sortType === 0) { 228 return defaultSort(callTreeLeftData, callTreeRightData); 229 } else if (this.sortType === 1) { 230 return `${callTreeLeftData.symbolName}`.localeCompare(`${callTreeRightData.symbolName}`); 231 } else { 232 return `${callTreeRightData.symbolName}`.localeCompare(`${callTreeLeftData.symbolName}`); 233 } 234 } else { 235 if (this.sortType === 0) { 236 return defaultSort(callTreeLeftData, callTreeRightData); 237 } else if (this.sortType === 1) { 238 return callTreeLeftData.totalTime - callTreeRightData.totalTime; 239 } else { 240 return callTreeRightData.totalTime - callTreeLeftData.totalTime; 241 } 242 } 243 }); 244 245 CallTreeSortArr.map((call) => { 246 call.children = this.sortTree(call.children); 247 }); 248 return CallTreeSortArr; 249 } 250 251 private clearTab(): void { 252 this.stackTable!.recycleDataSource = []; 253 this.callTreeTable!.recycleDataSource = []; 254 } 255 256 public initHtml(): string { 257 return TabPaneJsCpuHtml; 258 } 259} 260