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