1/* 2 * Copyright (C) 2024 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 { SelectionParam } from '../../../../bean/BoxSelection'; 18import { LitTable, RedrawTreeForm } from '../../../../../base-ui/table/lit-table'; 19import '../../../../../base-ui/slicer/lit-slicer'; 20import '../../../../../base-ui/progress-bar/LitProgressBar'; 21import { LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar'; 22import { procedurePool } from '../../../../database/Procedure'; 23import { Utils } from '../../base/Utils'; 24import { FrameChart } from '../../../chart/FrameChart'; 25import { FilterData, TabPaneFilter } from '../TabPaneFilter'; 26import { ChartMode } from '../../../../bean/FrameChartStruct'; 27 28@element('tabpane-perf-async') 29export class TabPanePerfAsync extends BaseElement { 30 private mainTable: LitTable | undefined | null; 31 private showTable: LitTable | undefined | null; 32 private callStackTable: LitTable | undefined | null; 33 private progressEl: LitProgressBar | undefined; 34 private currentSelection: SelectionParam | undefined | null; 35 private currentData: Array<perfAsyncList> = []; 36 private simpleData: Array<perfAsyncList> = []; 37 private isChartShow: boolean = false; 38 private asyncFilter: TabPaneFilter | null | undefined; 39 private asyncFrameChart: FrameChart | null | undefined; 40 private searchValue: string = ''; 41 private isSearch: boolean = false; 42 private processMap: Map<number, string> = new Map(); 43 private threadMap: Map<number, string> = new Map(); 44 45 /** 46 * 标签页点击后的入口 47 */ 48 set data(perfAsyncSelection: SelectionParam | null | undefined) { 49 if (perfAsyncSelection === this.currentSelection) { 50 return; 51 } 52 this.searchValue = ''; 53 // 保存当前的框选区域 54 this.currentSelection = perfAsyncSelection; 55 // 设置筛选框内容为空,切换图标显示成对应火焰图的 56 this.asyncFilter!.filterValue = ''; 57 this.asyncFilter!.icon = 'block'; 58 // 私有变量存储进程与线程的Map,后续根据id找对应的名字 59 this.processMap = Utils.getInstance().getProcessMap(); 60 this.threadMap = Utils.getInstance().getThreadMap(); 61 // 设置加载动画 62 this.progressEl!.loading = true; 63 // 提交子线程进行数据查询 64 this.submitQueryData( 65 { 66 tid: perfAsyncSelection?.perfThread!, 67 cpu: perfAsyncSelection?.perfCpus!, 68 pid: perfAsyncSelection?.perfProcess!, 69 leftNs: perfAsyncSelection?.leftNs!, 70 rightNs: perfAsyncSelection?.rightNs!, 71 eventId: perfAsyncSelection?.perfEventTypeId, 72 searchValue: this.searchValue 73 } 74 ); 75 } 76 77 /** 78 * 提交线程查询数据库获取数据 79 * @param args 框选的perf泳道线程id等相关数据构成的对象 80 */ 81 submitQueryData(args: { 82 tid?: Array<number>, 83 cpu?: Array<number>, 84 pid?: Array<number>, 85 leftNs: number, 86 rightNs: number, 87 eventId: number | undefined, 88 searchValue: string 89 }): void { 90 // 清空数据,保证筛选后没有数据时,相关的表格火焰图不会显示上次的结果 91 this.mainTable!.recycleDataSource = []; 92 this.showTable!.recycleDataSource = []; 93 this.callStackTable!.recycleDataSource = []; 94 this.asyncFrameChart!.data = []; 95 procedurePool.submitWithName('logic0', 'perf-async', args, undefined, this.callBack.bind(this)); 96 } 97 98 /** 99 * worker线程返回查询结果后执行的回调函数 100 * @param result 数据库查询结果 101 */ 102 callBack(result: Array<perfAsyncList>): void { 103 // 若返回数据为空,跳出函数 104 if (result.length === 0) { 105 this.progressEl!.loading = false; 106 // 切换显示火焰图还是表格 107 this.switchTableOrChart(); 108 return; 109 } 110 // 用临时变量接收处理完的数据 111 this.currentData = this.organizeData(result); 112 // 简表使用数据 113 this.simpleData = this.generateSimpleData(this.simpleData); 114 // 设置表格数据 115 // 设置表格展开 116 if (this.isSearch) { 117 this.showTable!.isSearch = true; 118 this.showTable!.setStatus(this.simpleData, true); 119 this.isSearch = false; 120 } 121 this.tHeadClick(this.simpleData); 122 // 设置简表数据 123 this.showTable!.recycleDataSource = this.simpleData; 124 // 设置详表数据 125 this.mainTable!.recycleDataSource = this.currentData; 126 // 更新火焰图canvas 127 this.asyncFrameChart?.updateCanvas(true, this.clientWidth); 128 // 设置火焰图根据sampleCount还是eventCount分布绘制 129 if (this.currentSelection!.perfEventTypeId === undefined) { 130 this.asyncFrameChart!.mode = ChartMode.Count; 131 } else { 132 this.asyncFrameChart!.mode = ChartMode.EventCount; 133 } 134 // 设置火焰图数据 135 // @ts-ignore 136 this.asyncFrameChart!.data = this.currentData; 137 // 切换显示火焰图还是表格 138 this.switchTableOrChart(); 139 this.progressEl!.loading = false; 140 }; 141 142 /** 143 * 合并规则,进程->线程->callerStack_Id->栈顶->第一非相同栈->AsyncWorkCallback下一层结束 144 * @param result 数据库查询结果 145 * @returns 整理好的结果 146 */ 147 organizeData(result: Array<perfAsyncList>): Array<perfAsyncList> { 148 // 最终显示到表格上的数组数据 149 let finalResult: Array<perfAsyncList> = []; 150 for (let i = 0; i < result.length; i++) { 151 // 处理每一条数据,配置基本项,并进行被调用栈切割,只保留asynccallback后的数据 152 this.dealEveryData(result[i]); 153 // 拷贝临时数组处理,否则会清掉引用类型变量原址的数据 154 let callStack: Array<perfAsyncList> = result[i].callerCallStack?.concat(result[i].calleeCallStack!)!; 155 // 进程数据 156 let processItem: perfAsyncList = { ...result[i] }; 157 processItem.isProcess = true; 158 processItem.symbol = this.processMap.get(result[i].pid!) === null ? 159 'Process[' + result[i].pid! + ']' : this.processMap.get(result[i].pid!)! + '[' + result[i].pid! + ']'; 160 // 线程数据 161 let threadItem: perfAsyncList = { ...result[i] }; 162 threadItem.isThread = true; 163 threadItem.symbol = this.threadMap.get(result[i].tid!) === null ? 164 'Thread[' + result[i].tid! + ']' : this.threadMap.get(result[i].tid!)! + '[' + result[i].tid! + ']'; 165 // @ts-ignore 166 callStack.unshift(threadItem); 167 // @ts-ignore 168 callStack.unshift(processItem); 169 // 递归将一维数组整理成链表 170 let callStackList: Array<perfAsyncList> = this.recursionToTree(callStack!, result[i])!; 171 // 如果两条数据calleechainid相同,则只保留前一条,并让sampleCount+1。如果不同,则找到被调用栈一样的的最后一层,将该条数据及其子数据插入进去 172 // 查找相同进程分组 173 const index: number = finalResult.findIndex((item) => item.pid === callStackList[0].pid); 174 if (index >= 0) { 175 finalResult[index].parent = undefined; 176 // 如果两条数据calleechainid相同,则只保留前一条,并让sampleCount+1。如果不同,则找到被调用栈一样的的最后一层,将该条数据及其子数据插入进去 177 recusion(callStackList[0], finalResult[index]); 178 } else { 179 finalResult.push(...callStackList); 180 } 181 } 182 // 保存数据,火焰图生成后处理简表数据 183 this.simpleData = [...result]; 184 return finalResult; 185 } 186 187 188 /** 189 * 修改每一条数据的对应字段显示值 190 * @param data 数据库查询结果中的每一条数据 191 */ 192 dealEveryData(data: perfAsyncList): void { 193 data.eventType = data.eventType + '[' + data.eventTypeId + ']'; 194 data.sampleCount = 1; 195 data.symbol = Utils.getTimeString(data.time!); 196 const index: number | undefined = data.calleeCallStack?.findIndex((item) => 197 item.symbolName!.indexOf('AsyncWorkCallback') !== -1 198 ); 199 // 若不保留AsyncWorkCallback这一层,则在截取时让index+1 200 data.calleeCallStack = data.calleeCallStack?.slice(index!)!; 201 data.callStackList = data.callerCallStack?.concat(data.calleeCallStack!); 202 data.jsFuncName = data.callerCallStack![data.callerCallStack!.length - 1].symbolName; 203 data.asyncFuncName = data.calleeCallStack!.length > 1 ? data.calleeCallStack![1].symbolName : data.calleeCallStack![0].symbolName; 204 data.drawCount = 0; 205 data.drawDur = 0; 206 data.drawEventCount = 0; 207 data.drawSize = 0; 208 data.searchCount = 0; 209 data.searchDur = 0; 210 data.searchEventCount = 0; 211 data.searchSize = 0; 212 data.isChartSelect = false; 213 data.isChartSelectParent = false; 214 data.isDraw = false; 215 data.isJsStack = false; 216 data.isProcess = false; 217 data.isThread = false; 218 if (data.isSearch === undefined) { 219 data.isSearch = false; 220 } 221 data.count = 1; 222 data.size = 0; 223 data.dur = 1; 224 data.isCharged = false; 225 } 226 227 /** 228 * 用于设置右侧调用栈与被调用栈表格信息 229 * @param data 当前主表点击的行数据 230 */ 231 setRightTableData(data: perfAsyncList): void { 232 const callStackList: Array<{ 233 head: string, 234 eventTypeId: number, 235 symbolName: string, 236 children: Array<perfAsyncList> 237 }> = []; 238 if (!(data.isProcess || data.isThread)) { 239 if (data.callerCallchainid) { 240 callStackList.push({ 241 head: '', 242 eventTypeId: 0, 243 symbolName: 'callerStack', 244 children: [] 245 }); 246 callStackList[0].children.push(...data.callerCallStack!); 247 if (data.calleeCallchainid) { 248 callStackList.push({ 249 head: '', 250 eventTypeId: 0, 251 symbolName: 'calleeStack', 252 children: [] 253 }); 254 callStackList[1].children.push(...data.calleeCallStack!); 255 } 256 } 257 } 258 this.callStackTable!.recycleDataSource = callStackList; 259 } 260 261 /** 262 * 过滤工具栏的功能函数 263 * @param data 过滤工具栏相关数据 264 */ 265 asyncListFilterGetFilter(data: FilterData): void { 266 if ((this.isChartShow && data.icon === 'tree') || (!this.isChartShow && data.icon === 'block')) { 267 this.switchTableOrChart(); 268 } else if (this.searchValue !== this.asyncFilter!.filterValue) { 269 this.searchValue = this.asyncFilter!.filterValue; 270 this.submitQueryData( 271 { 272 tid: this.currentSelection?.perfThread!, 273 cpu: this.currentSelection?.perfCpus!, 274 pid: this.currentSelection?.perfProcess!, 275 leftNs: this.currentSelection?.leftNs!, 276 rightNs: this.currentSelection?.rightNs!, 277 eventId: this.currentSelection?.perfEventTypeId, 278 searchValue: this.searchValue 279 } 280 ); 281 this.isSearch = true; 282 } else { 283 this.showTable!.setStatus(this.simpleData, true); 284 this.switchTableOrChart(); 285 } 286 } 287 288 /** 289 * 点击按钮进行表格与火焰图的切换 290 * @param data 过滤工具栏相关数据 291 */ 292 switchTableOrChart(): void { 293 let perfProfilerPageTab = this.shadowRoot?.querySelector('#show_table'); 294 let perfProfilerPageChart = this.shadowRoot?.querySelector('#show_chart'); 295 // 根据过滤工具栏的切换图标,显示火焰图或者表格 296 if (this.asyncFilter!.icon === 'block') { 297 perfProfilerPageChart?.setAttribute('class', 'show'); 298 perfProfilerPageTab?.setAttribute('class', ''); 299 this.isChartShow = true; 300 this.asyncFilter!.disabledMining = false; 301 this.asyncFrameChart?.calculateChartData(); 302 } else if (this.asyncFilter!.icon === 'tree') { 303 perfProfilerPageChart?.setAttribute('class', ''); 304 perfProfilerPageTab?.setAttribute('class', 'show'); 305 this.isChartShow = false; 306 this.asyncFilter!.disabledMining = false; 307 this.asyncFrameChart!.clearCanvas(); 308 this.showTable!.reMeauseHeight(); 309 } 310 } 311 312 /** 313 * 递归,将一维数组的每一项作为上一项的子项整理成链表结构 314 * @param list 待整理的一维数组 315 * @param data 每一项增加属性值时需要读取内容的数据 316 * @param flag 待传入的每一个节点 317 * @returns 返回数组结构,一项套一项,例:原数据为长度12的数组,返回则为嵌套深度为12的单项数组 318 */ 319 recursionToTree( 320 list: Array<perfAsyncList>, 321 data: perfAsyncList, 322 flag: Array<perfAsyncList> | null = null 323 ): Array<perfAsyncList> | undefined { 324 if (list.length === 0) { 325 return; 326 } 327 // 最后返回的数组,内容是一个链表 328 const tree: Array<perfAsyncList> = flag || []; 329 // 链表的每一层数据结构 330 let item: perfAsyncList = { ...data }; 331 item.children = []; 332 // 此处定义tsArray是为了避免引用类型变量在进行数据合并,出现最底层栈却有多条数据的问题 333 item.tsArray = [item.time!]; 334 item.parent = data; 335 // 循环一维数组,将每一项填充到其父级数组中 336 // @ts-ignore 337 item.symbol = list[0].symbolName || list[0].symbol; 338 // @ts-ignore 339 item.lib = list[0].lib || list[0].symbol; 340 // @ts-ignore 341 item.addr = list[0].addr || ''; 342 // @ts-ignore 343 item.isProcess = list[0].isProcess; 344 // @ts-ignore 345 item.isThread = list[0].isThread; 346 // 符合搜索字符串匹配的火焰图及表格特殊处理 347 if (this.searchValue !== '' && item.symbol!.toLocaleLowerCase().indexOf(this.searchValue.toLocaleLowerCase()) !== -1) { 348 item.isSearch = true; 349 } else { 350 item.isSearch = false; 351 } 352 tree.push(item); 353 // 处理完的数据进行删除,每次循环只会拿到首项,也就是需要处理的子项 354 list.splice(0, 1); 355 // 递归调用 356 this.recursionToTree(list, item, tree[0].children!); 357 return tree; 358 } 359 360 /** 361 * 将数据库返回数据进行整理,生成简表展示数据 362 * @param data 数据库返回数据 363 * @returns 简表展示的数据 364 */ 365 generateSimpleData(data: Array<perfAsyncList>): Array<perfAsyncList> { 366 let result = new Array<perfAsyncList>(); 367 for (let i = 0; i < data.length; i++) { 368 let list = new Array<perfAsyncList>(); 369 // 进程数据 370 let processItem: perfAsyncList = { ...data[i] }; 371 processItem.isProcess = true; 372 processItem.symbol = this.processMap.get(data[i].pid!) === null ? 373 'Process[' + data[i].pid! + ']' : this.processMap.get(data[i].pid!)! + '[' + data[i].pid! + ']'; 374 // 线程数据 375 let threadItem: perfAsyncList = { ...data[i] }; 376 threadItem.isThread = true; 377 threadItem.symbol = this.threadMap.get(data[i].tid!) === null ? 378 'Thread[' + data[i].tid! + ']' : this.threadMap.get(data[i].tid!)! + '[' + data[i].tid! + ']'; 379 // js栈层 380 let jsFuncItem: perfAsyncList = { ...data[i] }; 381 jsFuncItem.symbol = jsFuncItem.jsFuncName; 382 // asyncWorkCallBack下一层 383 let asyncFuncItem: perfAsyncList = { ...data[i] }; 384 asyncFuncItem.symbol = asyncFuncItem.asyncFuncName; 385 // 被调用栈最底层 386 let bottomItem: perfAsyncList = { ...data[i] }; 387 bottomItem.asyncFuncName = bottomItem.calleeCallStack![bottomItem.calleeCallStack!.length - 1].symbolName; 388 bottomItem.symbol = bottomItem.asyncFuncName; 389 // 填充到数组,整理成链表结构 390 list.push(processItem, threadItem, jsFuncItem, asyncFuncItem, bottomItem); 391 // @ts-ignore 392 list = this.recursionToTree(list, data[i])!; 393 // 查找相同进程分组 394 const index: number = result.findIndex((item) => item.pid === list[0].pid); 395 if (index >= 0) { 396 result[index].parent = undefined; 397 // 合并栈 398 recusion(list[0], result[index], true); 399 } else { 400 result.push(...list); 401 } 402 } 403 return result; 404 } 405 406 /** 407 * 表头点击事件 408 */ 409 private tHeadClick(data: Array<perfAsyncList>): void { 410 let labels = this.showTable?.shadowRoot?.querySelector('.th > .td')!.querySelectorAll('label'); 411 if (labels) { 412 for (let i = 0; i < labels.length; i++) { 413 let label = labels[i].innerHTML; 414 labels[i].addEventListener('click', (e) => { 415 if (label.includes('Process') && i === 0) { 416 this.showTable!.setStatus(data, false); 417 this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Retract); 418 } else if (label.includes('Thread') && i === 1) { 419 this.showTable!.setStatus(data, false, 0, 1); 420 this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Retract); 421 } else if (label.includes('Js_Func') && i === 2) { 422 this.showTable!.setStatus(data, false, 0, 2); 423 this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Retract); 424 } else if (label.includes('Async_Func') && i === 3) { 425 this.showTable!.setStatus(data, false, 0, 3); 426 this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Retract); 427 } else if (label.includes('Bottom') && i === 4) { 428 this.showTable!.setStatus(data, true); 429 this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Expand); 430 } 431 }); 432 } 433 } 434 } 435 436 /** 437 * 初始化元素,并进行变量绑定,监听事件添加 438 */ 439 initElements(): void { 440 this.mainTable = this.shadowRoot?.querySelector<LitTable>('#tb-perf-async'); 441 this.showTable = this.shadowRoot?.querySelector<LitTable>('#tb-perf-show'); 442 this.callStackTable = this.shadowRoot?.querySelector<LitTable>('#call-stack-list'); 443 this.progressEl = this.shadowRoot?.querySelector('.perf-async-progress') as LitProgressBar; 444 this.asyncFilter = this.shadowRoot?.querySelector<TabPaneFilter>('#filter'); 445 this.asyncFrameChart = this.shadowRoot?.querySelector<FrameChart>('#framechart'); 446 this.asyncFilter!.getFilterData(this.asyncListFilterGetFilter.bind(this)); 447 // 将主表绑定行点击事件,根据点击数据更新右侧的调用栈与被调用栈表格信息 448 // @ts-ignore 449 this.showTable?.addEventListener('row-click', (evt: CustomEvent) => { 450 let data = evt.detail.data; 451 data.isSelected = true; 452 this.showTable?.clearAllSelection(data); 453 this.showTable?.setCurrentSelection(data); 454 this.setRightTableData(data); 455 document.dispatchEvent( 456 new CustomEvent('number_calibration', { 457 detail: { time: data.tsArray }, 458 }) 459 ); 460 if (evt.detail.callBack) { 461 evt.detail.callBack(true); 462 } 463 }); 464 // @ts-ignore 465 this.callStackTable?.addEventListener('row-click', (evt: CustomEvent) => { 466 let data = evt.detail.data; 467 data.isSelected = true; 468 this.callStackTable?.clearAllSelection(data); 469 this.callStackTable?.setCurrentSelection(data); 470 if (evt.detail.callBack) { 471 evt.detail.callBack(true); 472 } 473 }); 474 } 475 476 /** 477 * 生命周期函数,用于调整表格高度 478 */ 479 connectedCallback(): void { 480 super.connectedCallback(); 481 // 设置火焰图滚动高度,更新火焰图时会 482 this.parentElement!.onscroll = (): void => { 483 this.asyncFrameChart!.tabPaneScrollTop = this.parentElement!.scrollTop; 484 }; 485 let filterHeight = 0; 486 // 动态监听视图窗口变化 487 new ResizeObserver((entries): void => { 488 let asyncTabFilter = this.shadowRoot!.querySelector('#filter') as HTMLElement; 489 // 获取过滤工具栏高度 490 if (asyncTabFilter.clientHeight > 0) { 491 filterHeight = asyncTabFilter.clientHeight; 492 } 493 // 动态调整过滤工具栏的显隐 494 if (this.parentElement!.clientHeight > filterHeight) { 495 asyncTabFilter.style.display = 'flex'; 496 } else { 497 asyncTabFilter.style.display = 'none'; 498 } 499 // 如果数据表隐藏,则让过滤工具栏也隐藏 500 if (this.showTable!.style.visibility === 'hidden') { 501 asyncTabFilter.style.display = 'none'; 502 } 503 if (this.parentElement?.clientHeight !== 0) { 504 // 更新火焰图 505 if (this.isChartShow) { 506 this.asyncFrameChart?.updateCanvas(false, entries[0].contentRect.width); 507 this.asyncFrameChart?.calculateChartData(); 508 } 509 // 调整表格高度 510 // @ts-ignore 511 this.showTable?.shadowRoot.querySelector('.table').style.height = 512 // @ts-ignore 513 `${this.parentElement.clientHeight - 45}px`; 514 this.showTable?.reMeauseHeight(); 515 // @ts-ignore 516 this.callStackTable?.shadowRoot.querySelector('.table').style.height = 517 `${this.parentElement!.clientHeight - 45}px`; 518 this.callStackTable?.reMeauseHeight(); 519 } 520 }).observe(this.parentElement!); 521 } 522 523 /** 524 * 前端渲染的节点及样式 525 * @returns html元素字符串 526 */ 527 initHtml(): string { 528 return this.initStyle() + ` 529 <div class="perf-async-content" style="display: flex;flex-direction: column"> 530 <lit-progress-bar class="progress perf-async-progress"></lit-progress-bar> 531 <selector id='show_table' class="show"> 532 <lit-slicer style="width:100%"> 533 <div id="left_table" style="width: 65%"> 534 <lit-table id="tb-perf-async" style="height: 100%; overflow: auto; display: none" tree> 535 <lit-table-column width="550px" title="CallStack" data-index="symbol" key="symbol" align="flex-start" retract></lit-table-column> 536 <lit-table-column width="150px" title="Event_Count" data-index="eventCount" key="eventCount" align="flex-start"></lit-table-column> 537 <lit-table-column width="100px" title="Sample_Count" data-index="sampleCount" key="sampleCount" align="flex-start"></lit-table-column> 538 <lit-table-column width="150px" title="Js_Func_Name" data-index="jsFuncName" key="jsFuncName" align="flex-start"></lit-table-column> 539 <lit-table-column width="150px" title="Async_Func_Name" data-index="asyncFuncName" key="asyncFuncName" align="flex-start"></lit-table-column> 540 <lit-table-column width="150px" title="Trace_Id" data-index="traceid" key="traceid" align="flex-start"></lit-table-column> 541 <lit-table-column width="150px" title="CallerStack_Id" data-index="callerCallchainid" key="callerCallchainid" align="flex-start"></lit-table-column> 542 <lit-table-column width="150px" title="CalleeStack_Id" data-index="calleeCallchainid" key="calleeCallchainid" align="flex-start"></lit-table-column> 543 </lit-table> 544 <lit-table id="tb-perf-show" style="height: 100%; overflow: auto;" tree hideDownload> 545 <lit-table-column width="550px" title="Process/Thread/Js_Func/Async_Func/Bottom" data-index="symbol" key="symbol" align="flex-start" retract></lit-table-column> 546 <lit-table-column width="150px" title="Event_Count" data-index="eventCount" key="eventCount" align="flex-start"></lit-table-column> 547 <lit-table-column width="100px" title="Sample_Count" data-index="sampleCount" key="sampleCount" align="flex-start"></lit-table-column> 548 <lit-table-column width="150px" title="Js_Func_Name" data-index="jsFuncName" key="jsFuncName" align="flex-start"></lit-table-column> 549 <lit-table-column width="150px" title="Async_Func_Name" data-index="asyncFuncName" key="asyncFuncName" align="flex-start"></lit-table-column> 550 <lit-table-column width="150px" title="Trace_Id" data-index="traceid" key="traceid" align="flex-start"></lit-table-column> 551 <lit-table-column width="150px" title="CallerStack_Id" data-index="callerCallchainid" key="callerCallchainid" align="flex-start"></lit-table-column> 552 <lit-table-column width="150px" title="CalleeStack_Id" data-index="calleeCallchainid" key="calleeCallchainid" align="flex-start"></lit-table-column> 553 </lit-table> 554 </div> 555 <lit-slicer-track ></lit-slicer-track> 556 <div id='right_table' style='height: 100%; overflow: auto;width: 35%'> 557 <lit-table id="call-stack-list" hideDownload tree> 558 <lit-table-column width="35px" title="" data-index="head" key="head" align="flex-start" retract></lit-table-column> 559 <lit-table-column width="30px" title="" data-index="eventTypeId" key="eventTypeId" align="flex-start"> 560 <template> 561 <img src="img/library.png" size="20" v-if=" eventTypeId == 1 "> <img src="img/function.png" size="20" v-if=" eventTypeId == 0 "> 562 </template> 563 </lit-table-column> 564 <lit-table-column width="1fr" title="Call_Stack" data-index="symbolName" key="symbolName" align="flex-start"> 565 </lit-table-column> 566 </lit-table> 567 </div> 568 </lit-slicer> 569 </selector> 570 <tab-pane-filter id="filter" input inputLeftText icon perf></tab-pane-filter> 571 <selector id='show_chart'> 572 <tab-framechart id='framechart' style='width: 100%;height: auto'> </tab-framechart> 573 </selector> 574 </div> 575 `; 576 } 577 578 /** 579 * 前端渲染节点的样式 580 * @returns style标签 581 */ 582 initStyle(): string { 583 return ` 584 <style> 585 :host{ 586 display: flex; 587 flex-direction: column; 588 padding: 10px 10px 0 10px; 589 height: calc(100% - 20px); 590 } 591 .perf-async-progress{ 592 position: absolute; 593 height: 1px; 594 left: 0; 595 right: 0; 596 } 597 selector{ 598 display: none; 599 } 600 .show{ 601 display: flex; 602 flex: 1; 603 } 604 #perf-async-menu { 605 position: fixed; 606 bottom: 0; 607 width: 100%; 608 border: solid rgb(216,216,216) 1px; 609 float: left; 610 background: #f3f3f3; 611 height: 30px; 612 } 613 .refresh { 614 padding-left: 5px; 615 padding-top: 5px; 616 } 617 tab-pane-filter { 618 position: fixed; 619 bottom: 0; 620 width: 100%; 621 border: solid rgb(216,216,216) 1px; 622 float: left; 623 } 624 .perf-profile-progress{ 625 bottom: 33px; 626 position: absolute; 627 height: 1px; 628 left: 0; 629 right: 0; 630 } 631 </style> 632 `; 633 } 634} 635 636interface perfAsyncList { 637 tid?: number; 638 pid?: number; 639 time?: number; 640 symbol?: string; 641 traceid?: string; 642 eventCount?: number; 643 sampleCount?: number; 644 jsFuncName?: string; 645 callerCallchainid?: number; 646 calleeCallchainid?: number; 647 asyncFuncName?: string; 648 eventType?: string; 649 children?: Array<perfAsyncList>; 650 eventTypeId?: number; 651 symbolName?: string; 652 callerCallStack?: Array<perfAsyncList>; 653 calleeCallStack?: Array<perfAsyncList>; 654 callStackList?: Array<perfAsyncList>; 655 parent?: perfAsyncList; 656 isProcess?: boolean; 657 isThread?: boolean; 658 depth?: number; 659 isSearch?: boolean; 660 isJsStack?: boolean; 661 lib?: string; 662 isChartSelectParent?: boolean; 663 isChartSelect?: boolean; 664 isDraw?: boolean; 665 drawDur?: number; 666 drawEventCount?: number; 667 drawCount?: number; 668 drawSize?: number; 669 searchEventCount?: number; 670 searchCount?: number; 671 searchDur?: number; 672 searchSize?: number; 673 size?: number; 674 count?: number; 675 dur?: number; 676 tsArray?: Array<number>; 677 isCharged?: boolean; 678 addr?: string; 679} 680 681/** 682 * 递归整理合并相同调用栈与被调用栈 683 * @param data 需要合并给目标数据的数据 684 * @param targetData 目标数据 685 * @param index 递归次数 686 */ 687export function recusion(data: perfAsyncList, targetData: perfAsyncList, flag?: boolean): void { 688 // 将新元素合并到目标元素时,将目标元素的sampleCount和eventCount进行累加,每次进来都是要合并的值 689 targetData.sampleCount! += data.sampleCount!; 690 targetData.count! += data.count!; 691 targetData.dur! += data.dur!; 692 targetData.eventCount! += data.eventCount!; 693 targetData.tsArray?.push(data.time!); 694 targetData.tsArray = [...new Set(targetData.tsArray)]; 695 data.parent = targetData.parent; 696 targetData.children?.sort((a, b) => b.count! - a.count!); 697 if (data.callerCallchainid !== targetData.callerCallchainid) { 698 targetData.jsFuncName = ''; 699 // @ts-ignore 700 targetData.callerCallchainid = ''; 701 targetData.traceid = ''; 702 } 703 if (data.calleeCallchainid !== targetData.calleeCallchainid) { 704 targetData.asyncFuncName = ''; 705 // @ts-ignore 706 targetData.calleeCallchainid = ''; 707 } 708 // 需要根据子级是否有值判断如何合并,两者都有子级 709 if (data.children!.length !== 0 && targetData.children!.length !== 0) { 710 // 目标栈可能已经保存了多条数据,需要找到合并栈子级与被合并栈子级相同的分支进行栈合并 711 // 筛选出子项最多的那个 712 const num: number = targetData?.children?.findIndex((item) => item.symbol === data.children![0].symbol)!; 713 // 去重 714 targetData.tsArray = [...new Set(targetData.tsArray)]; 715 // 若存在symbol相同的子级,则进入下一层合并。若不存在,则证明是新的分支,将新的子级填充到目标元素子级中 716 if (num >= 0) { 717 //此处是为了区分简表和详表。简表只有5层,存在首尾相同,中间不同的情况 718 if (flag && targetData.children![num].calleeCallchainid !== data.children![0].calleeCallchainid && data.children![0].children!.length === 0) { 719 targetData.children!.push(data.children![0]); 720 data.children![0].parent = targetData; 721 } else { 722 recusion(data.children![0], targetData.children![num], flag); 723 } 724 } else { 725 targetData.children?.push(data.children![0]); 726 data.children![0].parent = targetData; 727 targetData.children?.sort((a, b) => b.count! - a.count!); 728 } 729 } else if (data.children!.length !== 0 && targetData.children!.length === 0) { 730 // 若目标元素不存在子级,当前元素存在子级。证明目标元素中需要添加一个子级更多的元素,并将其填充到队首,方便查找相同分支时拿到的是子级更多的分支。 731 const num: number = targetData?.children?.findIndex((item) => item.symbol === data.children![0].symbol)!; 732 targetData.children?.splice(num, 0, data.children![0]); 733 data.children![0].parent = targetData; 734 } 735}