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, element } from '../../../../../base-ui/BaseElement'; 17import { type LitTable } from '../../../../../base-ui/table/lit-table'; 18import '../TabPaneFilter'; 19import { TabPaneFilter } from '../TabPaneFilter'; 20import { SelectionParam } from '../../../../bean/BoxSelection'; 21import '../../../chart/FrameChart'; 22import '../../../../../base-ui/slicer/lit-slicer'; 23import '../../../../../base-ui/progress-bar/LitProgressBar'; 24import { procedurePool } from '../../../../database/Procedure'; 25import { type LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar'; 26import { type PerfBottomUpStruct } from '../../../../bean/PerfBottomUpStruct'; 27import { findSearchNode, HiPerfStruct } from '../../../../database/ui-worker/ProcedureWorkerCommon'; 28 29@element('tabpane-perf-bottom-up') 30export class TabpanePerfBottomUp extends BaseElement { 31 private bottomUpTable: LitTable | null | undefined; 32 private stackTable: LitTable | null | undefined; 33 private sortKey = ''; 34 private sortType = 0; 35 private bottomUpSource: Array<PerfBottomUpStruct> = []; 36 private bottomUpFilter: TabPaneFilter | undefined | null; 37 private progressEL: LitProgressBar | null | undefined; 38 private searchValue: string = ''; 39 private currentSelection: SelectionParam | undefined; 40 private static instance: TabpanePerfBottomUp | null; 41 42 public initElements(): void { 43 this.bottomUpTable = this.shadowRoot?.querySelector('#callTreeTable') as LitTable; 44 this.stackTable = this.shadowRoot?.querySelector('#stackTable') as LitTable; 45 this.progressEL = this.shadowRoot?.querySelector('.progress') as LitProgressBar; 46 this.bottomUpFilter = this.shadowRoot?.querySelector('#filter') as TabPaneFilter; 47 this.bottomUpTable!.addEventListener('row-click', (evt) => this.bottomUpTableRowClickHandler(evt)); 48 this.stackTable!.addEventListener('row-click', (evt) => this.stackTableRowClick(evt)); 49 this.bottomUpTable!.addEventListener('column-click', (evt) => this.bottomUpTableColumnClickHandler(evt)); 50 this.bottomUpFilter!.getFilterData(() => { 51 if (this.searchValue !== this.bottomUpFilter!.filterValue) { 52 this.searchValue = this.bottomUpFilter!.filterValue; 53 HiPerfStruct.bottomFindCount = 0; 54 findSearchNode(this.bottomUpSource, this.searchValue, false); 55 } 56 if (HiPerfStruct.bottomFindCount === 0 && this.bottomUpFilter!.filterValue !== '') { 57 this.bottomUpTable!.recycleDataSource = []; 58 } else { 59 this.bottomUpTable!.setStatus(this.bottomUpSource, true); 60 this.setBottomUpTableData(this.bottomUpSource); 61 } 62 }); 63 } 64 65 public getBottomData(data: SelectionParam): void { 66 this.getDataByWorker(data, (results: Array<PerfBottomUpStruct>) => { 67 this.setBottomUpTableData(results); 68 }); 69 } 70 71 private getDataByWorker(val: SelectionParam, handler: (results: Array<PerfBottomUpStruct>) => void): void { 72 this.progressEL!.loading = true; 73 const args = [ 74 { 75 funcName: 'setSearchValue', 76 funcArgs: [''], 77 }, 78 { 79 funcName: 'getCurrentDataFromDbBottomUp', 80 funcArgs: [val], 81 }, 82 ]; 83 procedurePool.submitWithName('logic0', 'perf-action', args, undefined, (results: Array<PerfBottomUpStruct>) => { 84 handler(results); 85 this.progressEL!.loading = false; 86 }); 87 } 88 89 set data(data: SelectionParam) { 90 if (data == this.currentSelection) { 91 return; 92 } 93 this.currentSelection = data; 94 this.sortKey = ''; 95 this.sortType = 0; 96 this.bottomUpFilter!.filterValue = ''; 97 this.getBottomData(data); 98 } 99 100 private setBottomUpTableData(results: Array<PerfBottomUpStruct>): void { 101 const percentageDenominator = 100; 102 const percentFraction = 1; 103 this.stackTable!.recycleDataSource = []; 104 let sum = results.reduce( 105 (sum, struct) => { 106 sum.totalCount += struct.selfTime; 107 sum.totalEvent += struct.eventCount; 108 return sum; 109 }, 110 { 111 totalCount: 0, 112 totalEvent: 0, 113 } 114 ); 115 const setTabData = (array: Array<PerfBottomUpStruct>): void => { 116 array.forEach((data) => { 117 data.totalTimePercent = `${((data.totalTime / sum.totalCount) * percentageDenominator).toFixed( 118 percentFraction 119 )}%`; 120 data.selfTimePercent = `${((data.selfTime / sum.totalCount) * percentageDenominator).toFixed( 121 percentFraction 122 )}%`; 123 data.eventPercent = `${((data.eventCount / sum.totalEvent) * percentageDenominator).toFixed(percentFraction)}%`; 124 setTabData(data.children); 125 }); 126 }; 127 setTabData(results); 128 this.bottomUpSource = this.sortTree(results); 129 this.bottomUpTable!.recycleDataSource = this.bottomUpSource; 130 } 131 132 private bottomUpTableRowClickHandler(evt: Event): void { 133 const callStack: Array<PerfBottomUpStruct> = []; 134 const getCallStackChildren = (children: Array<PerfBottomUpStruct>): void => { 135 if (children.length === 0) { 136 return; 137 } 138 const heaviestChild = children.reduce((max, struct) => 139 Math.max(max.totalTime, struct.totalTime) === max.totalTime ? max : struct 140 ); 141 callStack?.push(heaviestChild); 142 getCallStackChildren(heaviestChild.children); 143 }; 144 const getParent = (list: PerfBottomUpStruct): void => { 145 if (list.parentNode && list.parentNode!.symbolName !== 'root') { 146 callStack.push(list.parentNode!); 147 getParent(list.parentNode!); 148 } 149 }; 150 151 //@ts-ignore 152 const bottomUpData = evt.detail.data as PerfBottomUpStruct; 153 document.dispatchEvent( 154 new CustomEvent('number_calibration', { 155 detail: { time: bottomUpData.tsArray }, 156 }) 157 ); 158 callStack!.push(bottomUpData); 159 if (bottomUpData.parentNode && bottomUpData.parentNode!.symbolName !== 'root') { 160 callStack.push(bottomUpData.parentNode!); 161 getParent(bottomUpData.parentNode!); 162 } 163 callStack.reverse(); 164 getCallStackChildren(bottomUpData.children); 165 this.stackTable!.recycleDataSource = callStack; 166 bottomUpData.isSelected = true; 167 this.stackTable?.clearAllSelection(bottomUpData); 168 this.stackTable?.setCurrentSelection(bottomUpData); 169 // @ts-ignore 170 if (evt.detail.callBack) { 171 // @ts-ignore 172 evt.detail.callBack(true); 173 } 174 } 175 176 private bottomUpTableColumnClickHandler(evt: Event): void { 177 // @ts-ignore 178 this.sortKey = evt.detail.key; 179 // @ts-ignore 180 this.sortType = evt.detail.sort; 181 this.setBottomUpTableData(this.bottomUpSource); 182 } 183 184 private stackTableRowClick(evt: Event): void { 185 //@ts-ignore 186 const data = evt.detail.data as PerfBottomUpStruct; 187 data.isSelected = true; 188 this.bottomUpTable!.clearAllSelection(data); 189 this.bottomUpTable!.scrollToData(data); 190 // @ts-ignore 191 if (evt.detail.callBack) { 192 // @ts-ignore 193 evt.detail.callBack(true); 194 } 195 } 196 197 public connectedCallback(): void { 198 const tableOffsetHeight = 32; 199 const spanHeight = 22; 200 super.connectedCallback(); 201 new ResizeObserver(() => { 202 // @ts-ignore 203 this.bottomUpTable?.shadowRoot.querySelector('.table').style.height = `${this.parentElement!.clientHeight - tableOffsetHeight 204 }px`; 205 this.bottomUpTable?.reMeauseHeight(); 206 // @ts-ignore 207 this.stackTable?.shadowRoot.querySelector('.table').style.height = `${this.parentElement!.clientHeight - tableOffsetHeight - spanHeight 208 }px`; 209 this.stackTable?.reMeauseHeight(); 210 }).observe(this.parentElement!); 211 } 212 213 private sortTree(arr: Array<PerfBottomUpStruct>): Array<PerfBottomUpStruct> { 214 const defaultSortType = 0; 215 216 function defaultSort(callTreeLeftData: PerfBottomUpStruct, callTreeRightData: PerfBottomUpStruct): number { 217 return callTreeRightData.totalTime - callTreeLeftData.totalTime; 218 } 219 220 const CallTreeSortArr = arr.sort((callTreeLeftData, callTreeRightData) => { 221 if (this.sortKey === 'selfTime' || this.sortKey === 'selfTimePercent') { 222 if (this.sortType === defaultSortType) { 223 return defaultSort(callTreeLeftData, callTreeRightData); 224 } else if (this.sortType === 1) { 225 return callTreeLeftData.selfTime - callTreeRightData.selfTime; 226 } else { 227 return callTreeRightData.selfTime - callTreeLeftData.selfTime; 228 } 229 } else if (this.sortKey === 'symbolName') { 230 if (this.sortType === defaultSortType) { 231 return defaultSort(callTreeLeftData, callTreeRightData); 232 } else if (this.sortType === 1) { 233 return `${callTreeLeftData.symbolName}`.localeCompare(`${callTreeRightData.symbolName}`); 234 } else { 235 return `${callTreeRightData.symbolName}`.localeCompare(`${callTreeLeftData.symbolName}`); 236 } 237 } else { 238 if (this.sortType === defaultSortType) { 239 return defaultSort(callTreeLeftData, callTreeRightData); 240 } else if (this.sortType === 1) { 241 return callTreeLeftData.totalTime - callTreeRightData.totalTime; 242 } else { 243 return callTreeRightData.totalTime - callTreeLeftData.totalTime; 244 } 245 } 246 }); 247 248 CallTreeSortArr.map((call) => { 249 call.children = this.sortTree(call.children); 250 }); 251 return CallTreeSortArr; 252 } 253 254 private initHtmlStyle(): string { 255 return ` 256 <style> 257 :host{ 258 display: flex; 259 flex-direction: column; 260 padding: 0 10px 0 10px; 261 } 262 .show-bottom-up{ 263 display: flex; 264 flex: 1; 265 } 266 .perf-bottom-up-progress{ 267 bottom: 33px; 268 position: absolute; 269 height: 1px; 270 left: 0; 271 right: 0; 272 } 273 </style> 274 `; 275 } 276 277 public initHtml(): string { 278 return ` 279 ${this.initHtmlStyle()} 280 <div class="perf-bottom-up-content"> 281 <selector id='show_table' class="show-bottom-up"> 282 <lit-slicer style="width:100%"> 283 <div id="left_table" style="width: 65%"> 284 <lit-table id="callTreeTable" style="height: 100%" tree> 285 <lit-table-column width="50%" title="Symbol" data-index="symbolName" key="symbolName" 286 align="flex-start" order retract></lit-table-column> 287 <lit-table-column width="1fr" title="Local" data-index="selfTime" key="selfTime" 288 align="flex-start" order></lit-table-column> 289 <lit-table-column width="1fr" title="%" data-index="selfTimePercent" key="selfTimePercent" 290 align="flex-start" order></lit-table-column> 291 <lit-table-column width="1fr" title="Sample Count" data-index="totalTime" key="totalTime" 292 align="flex-start" order></lit-table-column> 293 <lit-table-column width="1fr" title="%" data-index="totalTimePercent" key="totalTimePercent" 294 align="flex-start" order></lit-table-column> 295 <lit-table-column width="1fr" title="Event Count" data-index="eventCount" key="eventCount" 296 align="flex-start" order></lit-table-column> 297 <lit-table-column width="1fr" title="%" data-index="eventPercent" key="eventPercent" 298 align="flex-start" order></lit-table-column> 299 </lit-table> 300 </div> 301 <lit-slicer-track ></lit-slicer-track> 302 <div class="right" style="flex: 1;display: flex; flex-direction: row;"> 303 <div style="flex: 1;display: block;"> 304 <span slot="head" style="height: 22px">Call Stack</span> 305 <lit-table id="stackTable" style="height: auto;"> 306 <lit-table-column width="50%" title="Symbol" data-index="symbolName" key="symbolName" 307 align="flex-start"></lit-table-column> 308 <lit-table-column width="1fr" title="Sample Count" data-index="totalTime" key="totalTime" 309 align="flex-start" ></lit-table-column> 310 <lit-table-column width="1fr" title="%" data-index="totalTimePercent" key="totalTimePercent" 311 align="flex-start"></lit-table-column> 312 </lit-table> 313 </div> 314 </div> 315 </lit-slicer> 316 </selector> 317 <tab-pane-filter id="filter" input inputLeftText ></tab-pane-filter> 318 <lit-progress-bar class="progress perf-bottom-up-progress"></lit-progress-bar> 319 </div> 320 `; 321 } 322} 323