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