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.js"; 17import {LitTable} from "../../../../../base-ui/table/lit-table.js"; 18import {LitProgressBar} from "../../../../../base-ui/progress-bar/LitProgressBar.js"; 19import {FrameChart} from "../../../chart/FrameChart.js"; 20import {SelectionParam} from "../../../../bean/BoxSelection.js"; 21import {ChartMode} from "../../../../bean/FrameChartStruct.js"; 22import {FilterData, TabPaneFilter} from "../TabPaneFilter.js"; 23import {procedurePool} from "../../../../database/Procedure.js"; 24import {MerageBean} from "../../../../database/logic-worker/ProcedureLogicWorkerCommon.js"; 25 26@element('tabpane-calltree') 27export class TabPaneCallTree extends BaseElement { 28 public queryFuncName:string = "" 29 public procedureAction:string = "" 30 private tbl: LitTable | null | undefined; 31 private tbr: LitTable | null | undefined; 32 private progressEL:LitProgressBar | null | undefined; 33 private rightSource: Array<MerageBean> = []; 34 private filter: any 35 private dataSource: any[] = [] 36 private sortKey = "weight"; 37 private sortType = 0; 38 private currentSelectedData: any = undefined 39 private frameChart: FrameChart | null | undefined; 40 private isChartShow: boolean = false; 41 private systmeRuleName = "/system/" 42 private numRuleName = "/max/min/" 43 private needShowMenu = true; 44 private searchValue: string = "" 45 private loadingList:number[] = [] 46 private loadingPage:any; 47 private currentSelection:SelectionParam|undefined 48 private flameChartMode:ChartMode = ChartMode.Duration 49 50 set data(val: SelectionParam | any) { 51 this.initModeAndAction() 52 this.currentSelection = val 53 this.tbl!.style.visibility = "visible"; 54 if (this.parentElement!.clientHeight > this.filter!.clientHeight) { 55 this.filter!.style.display = "flex"; 56 } else { 57 this.filter!.style.display = "none"; 58 } 59 this.filter!.initializeFilterTree(true, true, true) 60 this.filter!.filterValue = "" 61 this.progressEL!.loading = true 62 this.loadingPage.style.visibility = "visible" 63 this.getDataByWorker([{ 64 funcName: "setSearchValue", 65 funcArgs: [""] 66 }, { 67 funcName: "getCurrentDataFromDb", 68 funcArgs: [{queryFuncName:this.queryFuncName,...val}] 69 }], (results: any[]) => { 70 this.setLTableData(results) 71 this.tbr!.recycleDataSource = [] 72 this.frameChart!.mode = this.flameChartMode; 73 this.frameChart!.data = this.dataSource; 74 this.frameChart?.updateCanvas(true, this.clientWidth); 75 this.frameChart?.calculateChartData(); 76 }) 77 } 78 79 initModeAndAction(){ 80 if (this.procedureAction == ""&&this.hasAttribute("action")) { 81 this.procedureAction = this.getAttribute("action")||"" 82 } 83 if(this.hasAttribute("flame-mode")){ 84 let mode = this.getAttribute("flame-mode") 85 switch (mode){ 86 case "Call": 87 this.flameChartMode = ChartMode.Call 88 break; 89 case "Byte": 90 this.flameChartMode = ChartMode.Byte 91 break; 92 case "Count": 93 this.flameChartMode = ChartMode.Count 94 break; 95 case "Duration": 96 this.flameChartMode = ChartMode.Duration 97 break; 98 } 99 } 100 if(this.hasAttribute("query")){ 101 this.queryFuncName = this.getAttribute("query")||"" 102 } 103 } 104 105 getParentTree(src: Array<MerageBean>, target: MerageBean, parents: Array<MerageBean>): boolean { 106 for (let call of src) { 107 if (call.id == target.id) { 108 parents.push(call) 109 return true 110 } else { 111 if (this.getParentTree(call.children as Array<MerageBean>, target, parents)) { 112 parents.push(call); 113 return true; 114 } 115 } 116 } 117 return false; 118 } 119 120 getChildTree(src: Array<MerageBean>, id: string, children: Array<MerageBean>): boolean { 121 for (let call of src) { 122 if (call.id == id && call.children.length == 0) { 123 children.push(call) 124 return true 125 } else { 126 if (this.getChildTree(call.children as Array<MerageBean>, id, children)) { 127 children.push(call); 128 return true; 129 } 130 } 131 } 132 return false; 133 } 134 135 setRightTableData(call: MerageBean) { 136 let parents: Array<MerageBean> = []; 137 let children: Array<MerageBean> = []; 138 this.getParentTree(this.dataSource, call, parents); 139 let maxId = call.id; 140 let maxDur = 0; 141 142 function findMaxStack(call: MerageBean) { 143 if (call.children.length == 0) { 144 if (call.dur > maxDur) { 145 maxDur = call.dur; 146 maxId = call.id; 147 } 148 } else { 149 call.children.map((callChild:any) => { 150 findMaxStack(<MerageBean>callChild); 151 }) 152 } 153 } 154 155 findMaxStack(call); 156 this.getChildTree(call.children as Array<MerageBean>, maxId, children); 157 let arr = parents.reverse().concat(children.reverse()); 158 for (let data of arr) { 159 data.type = (data.libName.endsWith(".so.1") || data.libName.endsWith(".dll") || data.libName.endsWith(".so")) ? 0 : 1; 160 } 161 let len = arr.length; 162 this.rightSource = arr; 163 this.tbr!.dataSource = len == 0 ? [] : arr 164 } 165 166 showButtomMenu(isShow: boolean) { 167 if (isShow) { 168 this.filter.setAttribute('tree', ''); 169 this.filter.setAttribute('input', ''); 170 this.filter.setAttribute('inputLeftText', ''); 171 } else { 172 this.filter.removeAttribute('tree'); 173 this.filter.removeAttribute('input'); 174 this.filter.removeAttribute('inputLeftText') 175 } 176 } 177 178 connectedCallback() { 179 this.parentElement!.onscroll = () => { 180 this.frameChart!.tabPaneScrollTop = this.parentElement!.scrollTop; 181 }; 182 this.frameChart!.addChartClickListener((needShowMenu: boolean) => { 183 this.parentElement!.scrollTo(0, 0); 184 this.showButtomMenu(needShowMenu) 185 this.needShowMenu = needShowMenu; 186 }); 187 let filterHeight = 0; 188 new ResizeObserver((entries) => { 189 let tabPaneFilter = this.shadowRoot!.querySelector("#filter") as HTMLElement; 190 if (tabPaneFilter.clientHeight > 0) filterHeight = tabPaneFilter.clientHeight; 191 if (this.parentElement!.clientHeight > filterHeight) { 192 tabPaneFilter.style.display = "flex"; 193 } else { 194 tabPaneFilter.style.display = "none"; 195 } 196 if (this.tbl!.style.visibility == "hidden") { 197 tabPaneFilter.style.display = "none"; 198 } 199 if (this.parentElement?.clientHeight != 0) { 200 if (this.isChartShow) { 201 this.frameChart?.updateCanvas(false, entries[0].contentRect.width); 202 this.frameChart?.calculateChartData(); 203 } 204 // @ts-ignore 205 this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 10 - 35) + "px" 206 this.tbl?.reMeauseHeight() 207 // @ts-ignore 208 this.tbr?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 45 - 21) + "px" 209 this.tbr?.reMeauseHeight() 210 this.loadingPage.style.height = (this.parentElement!.clientHeight - 24) + "px" 211 } 212 }).observe(this.parentElement!) 213 } 214 215 initElements(): void { 216 this.tbl = this.shadowRoot?.querySelector<LitTable>('#tb-calltree'); 217 this.progressEL = this.shadowRoot?.querySelector('.progress') as LitProgressBar 218 this.frameChart = this.shadowRoot?.querySelector<FrameChart>('#framechart'); 219 this.loadingPage = this.shadowRoot?.querySelector('.loading'); 220 221 this.tbl!.rememberScrollTop = true; 222 this.filter = this.shadowRoot?.querySelector<TabPaneFilter>("#filter") 223 this.tbl!.addEventListener('row-click', (evt: any) => { 224 // @ts-ignore 225 let data = (evt.detail.data as MerageBean); 226 this.setRightTableData(data); 227 data.isSelected = true; 228 this.currentSelectedData = data; 229 this.tbr?.clearAllSelection(data); 230 this.tbr?.setCurrentSelection(data); 231 // @ts-ignore 232 if ((evt.detail as any).callBack) { 233 // @ts-ignore 234 (evt.detail as any).callBack(true) 235 } 236 }) 237 this.tbr = this.shadowRoot?.querySelector<LitTable>('#tb-list'); 238 this.tbr!.addEventListener('row-click', (evt: any) => { 239 // @ts-ignore 240 let data = (evt.detail.data as MerageBean); 241 this.tbl?.clearAllSelection(data); 242 (data as any).isSelected = true 243 this.tbl!.scrollToData(data) 244 // @ts-ignore 245 if ((evt.detail as any).callBack) { 246 // @ts-ignore 247 (evt.detail as any).callBack(true) 248 } 249 }) 250 let filterFunc = (data: any) => { 251 let args: any[] = [] 252 if (data.type == "check") { 253 if (data.item.checked) { 254 args.push({ 255 funcName: "splitTree", 256 funcArgs: [data.item.name, data.item.select == "0", data.item.type == "symbol"] 257 }) 258 } else { 259 args.push({ 260 funcName: "resotreAllNode", 261 funcArgs: [[data.item.name]] 262 }) 263 args.push({ 264 funcName: "resetAllNode", 265 funcArgs: [] 266 }) 267 args.push({ 268 funcName: "clearSplitMapData", 269 funcArgs: [data.item.name] 270 }) 271 } 272 } else if (data.type == "select") { 273 args.push({ 274 funcName: "resotreAllNode", 275 funcArgs: [[data.item.name]] 276 }) 277 args.push({ 278 funcName: "clearSplitMapData", 279 funcArgs: [data.item.name] 280 }) 281 args.push({ 282 funcName: "splitTree", 283 funcArgs: [data.item.name, data.item.select == "0", data.item.type == "symbol"] 284 }) 285 } else if (data.type == "button") { 286 if (data.item == "symbol") { 287 if (this.currentSelectedData && !this.currentSelectedData.canCharge) { 288 return 289 } 290 if (this.currentSelectedData != undefined) { 291 this.filter!.addDataMining({name: this.currentSelectedData.symbolName}, data.item) 292 args.push({ 293 funcName: "splitTree", 294 funcArgs: [this.currentSelectedData.symbolName, false, true] 295 }) 296 } else { 297 return 298 } 299 } else if (data.item == "library") { 300 if (this.currentSelectedData && !this.currentSelectedData.canCharge) { 301 return 302 } 303 if (this.currentSelectedData != undefined && this.currentSelectedData.libName != "") { 304 this.filter!.addDataMining({name: this.currentSelectedData.libName}, data.item) 305 args.push({ 306 funcName: "splitTree", 307 funcArgs: [this.currentSelectedData.libName, false, false] 308 }) 309 } else { 310 return 311 } 312 } else if (data.item == "restore") { 313 if (data.remove != undefined && data.remove.length > 0) { 314 let list = data.remove.map((item: any) => { 315 return item.name 316 }) 317 args.push({ 318 funcName: "resotreAllNode", 319 funcArgs: [list] 320 }) 321 args.push({ 322 funcName: "resetAllNode", 323 funcArgs: [] 324 }) 325 list.forEach((symbolName: string) => { 326 args.push({ 327 funcName: "clearSplitMapData", 328 funcArgs: [symbolName] 329 }) 330 }) 331 } 332 } 333 } 334 this.getDataByWorker(args, (result: any[]) => { 335 this.setLTableData(result) 336 this.frameChart!.data = this.dataSource; 337 if (this.isChartShow) this.frameChart?.calculateChartData(); 338 this.tbl!.move1px() 339 if (this.currentSelectedData) { 340 this.currentSelectedData.isSelected = false; 341 this.tbl?.clearAllSelection(this.currentSelectedData) 342 this.tbr!.recycleDataSource = [] 343 this.currentSelectedData = undefined 344 } 345 }) 346 } 347 this.filter!.getDataLibrary(filterFunc) 348 this.filter!.getDataMining(filterFunc) 349 this.filter!.getCallTreeData((data: any) => { 350 if (data.value == 0) { 351 this.refreshAllNode({...this.filter!.getFilterTreeData(), callTree: data.checks}) 352 } else { 353 let args: any[] = [] 354 if (data.checks[1]) { 355 args.push({ 356 funcName: "hideSystemLibrary", 357 funcArgs: [] 358 }) 359 args.push({ 360 funcName: "resetAllNode", 361 funcArgs: [] 362 }) 363 } else { 364 args.push({ 365 funcName: "resotreAllNode", 366 funcArgs: [[this.systmeRuleName]] 367 }) 368 args.push({ 369 funcName: "resetAllNode", 370 funcArgs: [] 371 }) 372 args.push({ 373 funcName: "clearSplitMapData", 374 funcArgs: [this.systmeRuleName] 375 }) 376 } 377 this.getDataByWorker(args, (result: any[]) => { 378 this.setLTableData(result) 379 this.frameChart!.data = this.dataSource; 380 if (this.isChartShow) this.frameChart?.calculateChartData(); 381 }) 382 383 } 384 }) 385 this.filter!.getCallTreeConstraintsData((data: any) => { 386 let args: any[] = [{ 387 funcName: "resotreAllNode", 388 funcArgs: [[this.numRuleName]] 389 }, { 390 funcName: "clearSplitMapData", 391 funcArgs: [this.numRuleName] 392 }] 393 if (data.checked) { 394 args.push({ 395 funcName: "hideNumMaxAndMin", 396 funcArgs: [parseInt(data.min), data.max] 397 }) 398 } 399 args.push({ 400 funcName: "resetAllNode", 401 funcArgs: [] 402 }) 403 this.getDataByWorker(args, (result: any[]) => { 404 this.setLTableData(result) 405 this.frameChart!.data = this.dataSource; 406 if (this.isChartShow) this.frameChart?.calculateChartData(); 407 }) 408 409 }) 410 this.filter!.getFilterData((data: FilterData) => { 411 if (this.searchValue != this.filter!.filterValue) { 412 this.searchValue = this.filter!.filterValue 413 let args = [ 414 { 415 funcName: "setSearchValue", 416 funcArgs: [this.searchValue] 417 }, 418 { 419 funcName: "resetAllNode", 420 funcArgs: [] 421 } 422 ] 423 this.getDataByWorker(args, (result: any[]) => { 424 this.setLTableData(result) 425 this.frameChart!.data = this.dataSource; 426 this.switchFlameChart(data) 427 }) 428 }else { 429 this.switchFlameChart(data) 430 } 431 432 }) 433 this.tbl!.addEventListener('column-click', (evt) => { 434 // @ts-ignore 435 this.sortKey = evt.detail.key 436 // @ts-ignore 437 this.sortType = evt.detail.sort 438 // @ts-ignore 439 this.setLTableData(this.dataSource) 440 this.frameChart!.data = this.dataSource; 441 }); 442 } 443 444 switchFlameChart(data:any){ 445 let pageTab = this.shadowRoot?.querySelector('#show_table'); 446 let pageChart = this.shadowRoot?.querySelector('#show_chart'); 447 if (data.icon == 'block') { 448 pageChart?.setAttribute('class', 'show'); 449 pageTab?.setAttribute('class', ''); 450 this.isChartShow = true; 451 this.filter!.disabledMining = true; 452 this.showButtomMenu(this.needShowMenu); 453 this.frameChart!.data = this.dataSource; 454 this.frameChart?.calculateChartData(); 455 } else if (data.icon == 'tree') { 456 pageChart?.setAttribute('class', ''); 457 pageTab?.setAttribute('class', 'show'); 458 this.showButtomMenu(true); 459 this.isChartShow = false; 460 this.filter!.disabledMining = false; 461 this.frameChart!.clearCanvas(); 462 this.tbl!.reMeauseHeight() 463 } 464 } 465 466 467 refreshAllNode(filterData: any) { 468 let args:any[] = [] 469 let isTopDown: boolean = !filterData.callTree[0]; 470 let isHideSystemLibrary = filterData.callTree[1]; 471 let list = filterData.dataMining.concat(filterData.dataLibrary); 472 args.push({ 473 funcName: "getCallChainsBySampleIds", 474 funcArgs: [isTopDown] 475 }) 476 this.tbr!.recycleDataSource = [] 477 if (isHideSystemLibrary) { 478 args.push({ 479 funcName: "hideSystemLibrary", 480 funcArgs: [] 481 }) 482 } 483 if (filterData.callTreeConstraints.checked) { 484 args.push({ 485 funcName: "hideNumMaxAndMin", 486 funcArgs: [parseInt(filterData.callTreeConstraints.inputs[0]), filterData.callTreeConstraints.inputs[1]] 487 }) 488 } 489 args.push({ 490 funcName: "splitAllProcess", 491 funcArgs: [list] 492 }) 493 args.push({ 494 funcName: "resetAllNode", 495 funcArgs: [] 496 }) 497 this.getDataByWorker(args, (result: any[]) => { 498 this.setLTableData(result) 499 this.frameChart!.data = this.dataSource; 500 if (this.isChartShow) this.frameChart?.calculateChartData(); 501 }) 502 } 503 504 setLTableData(resultData:any[]) { 505 this.dataSource = this.sortTree(resultData) 506 this.tbl!.recycleDataSource = this.dataSource 507 } 508 509 sortTree(arr: Array<any>): Array<any> { 510 let sortArr = arr.sort((a, b) => { 511 if (this.sortKey == 'self') { 512 if (this.sortType == 0) { 513 return b.dur - a.dur; 514 } else if (this.sortType == 1) { 515 return a.selfDur - b.selfDur; 516 } else { 517 return b.selfDur - a.selfDur; 518 } 519 } else { 520 if (this.sortType == 0) { 521 return b.dur - a.dur; 522 } else if (this.sortType == 1) { 523 return a.dur - b.dur; 524 } else { 525 return b.dur - a.dur; 526 } 527 } 528 }) 529 sortArr.map((call) => { 530 call.children = this.sortTree(call.children); 531 }) 532 return sortArr; 533 } 534 535 getDataByWorker(args: any[], handler: Function) { 536 this.loadingList.push(1) 537 this.progressEL!.loading = true 538 this.loadingPage.style.visibility = "visible" 539 procedurePool.submitWithName("logic0",this.procedureAction,args,undefined,(results:any)=>{ 540 handler(results) 541 this.loadingList.splice(0,1) 542 if(this.loadingList.length == 0) { 543 this.progressEL!.loading = false 544 this.loadingPage.style.visibility = "hidden" 545 } 546 }) 547 } 548 549 initHtml(): string { 550 return ` 551 <style> 552 :host{ 553 display: flex; 554 flex-direction: column; 555 padding: 10px 10px 0 10px; 556 } 557 tab-pane-filter { 558 border: solid rgb(216,216,216) 1px; 559 float: left; 560 position: fixed; 561 bottom: 0; 562 width: 100%; 563 } 564 selector{ 565 display: none; 566 } 567 .show{ 568 display: flex; 569 flex: 1; 570 } 571 .progress{ 572 bottom: 33px; 573 position: absolute; 574 height: 1px; 575 left: 0; 576 right: 0; 577 } 578 .loading{ 579 bottom: 0; 580 position: absolute; 581 left: 0; 582 right: 0; 583 width:100%; 584 background:transparent; 585 z-index: 999999; 586 } 587 </style> 588 <div style="display: flex;flex-direction: row"> 589 590 <selector id='show_table' class="show"> 591 <lit-slicer style="width:100%"> 592 <div id="left_table" style="width: 65%"> 593 <lit-table id="tb-calltree" style="height: auto" tree> 594 <lit-table-column width="70%" title="Call Stack" data-index="symbolName" key="symbolName" align="flex-start" ></lit-table-column> 595 <lit-table-column width="1fr" title="Local" data-index="self" key="self" align="flex-start" order></lit-table-column> 596 <lit-table-column width="1fr" title="Weight" data-index="weight" key="weight" align="flex-start" order></lit-table-column> 597 <lit-table-column width="1fr" title="%" data-index="weightPercent" key="weightPercent" align="flex-start" order></lit-table-column> 598 </lit-table> 599 600 </div> 601 <lit-slicer-track ></lit-slicer-track> 602 <lit-table id="tb-list" no-head style="height: auto;border-left: 1px solid var(--dark-border1,#e2e2e2)"> 603 <span slot="head">Heaviest Stack Trace</span> 604 <lit-table-column width="30px" title="" data-index="type" key="type" align="flex-start" > 605 <template> 606 <img src="img/library.png" size="20" v-if=" type == 1 "> 607 <img src="img/function.png" size="20" v-if=" type == 0 "> 608 </template> 609 </lit-table-column> 610 <lit-table-column width="60px" title="" data-index="count" key="count" align="flex-start"></lit-table-column> 611 <lit-table-column width="1fr" title="" data-index="symbolName" key="symbolName" align="flex-start"></lit-table-column> 612 </lit-table> 613 </div> 614 </lit-slicer> 615 </selector> 616 <tab-pane-filter id="filter" input inputLeftText icon tree></tab-pane-filter> 617 <lit-progress-bar class="progress"></lit-progress-bar> 618 <selector id='show_chart'> 619 <tab-framechart id='framechart' style='width: 100%;height: auto'> </tab-framechart> 620 </selector> 621 <div class="loading"></div> 622 </div>`; 623 } 624 625}