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