1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { BaseElement, element } from '../../../../../base-ui/BaseElement'; 17import { SelectionParam } from '../../../../bean/BoxSelection'; 18import { ColorUtils } from '../../base/ColorUtils'; 19import { LitTable } from '../../../../../base-ui/table/lit-table'; 20import { LitIcon } from '../../../../../base-ui/icon/LitIcon'; 21import { TabPaneHangSummaryHtml } from '../hang/TabPaneHangSummary.html'; 22import { NUM_30, NUM_40 } from '../../../../bean/NumBean'; 23import { HangStruct } from '../../../../database/ui-worker/ProcedureWorkerHang'; 24import { HangType, SpHangChart } from '../../../chart/SpHangChart'; 25import { queryAllHangs } from '../../../../database/sql/Hang.sql'; 26 27/// Hangs 框选Tab页2 28@element('tab-hang-summary') 29export class TabPaneHangSummary extends BaseElement { 30 private hangSummaryTable: HTMLDivElement | undefined | null; 31 private summaryDownLoadTbl: LitTable | undefined | null; 32 private systemHangSource: HangStruct[] = []; 33 private hangTreeNodes: HangTreeNode[] = []; 34 private expansionDiv: HTMLDivElement | undefined | null; 35 private expansionUpIcon: LitIcon | undefined | null; 36 private expansionDownIcon: LitIcon | undefined | null; 37 private expandedNodeList: Set<number> = new Set(); 38 private hangLevel: string[] = ['Instant', 'Circumstantial', 'Micro', 'Severe']; 39 private selectTreeDepth: number = 0; 40 private currentSelection: SelectionParam | undefined; 41 42 /// 框选时段范围时触发 43 set data(selectionParam: SelectionParam) { 44 if (selectionParam === this.currentSelection) { 45 return; 46 } 47 this.currentSelection = selectionParam; 48 this.expandedNodeList.clear(); 49 this.expansionUpIcon!.name = 'up'; 50 this.expansionDownIcon!.name = 'down'; 51 this.hangSummaryTable!.innerHTML = ''; 52 this.summaryDownLoadTbl!.recycleDataSource = []; 53 queryAllHangs().then((ret) => { 54 const filter = new Set([...selectionParam.hangMapData.keys()].map(key => key.split(' ').at(-1))); 55 ret = ret.filter(struct => ( 56 filter.has(`${struct.pid ?? 0}`) && 57 ((struct.startTime ?? 0) <= selectionParam.rightNs) && 58 (selectionParam.leftNs <= ((struct.startTime ?? 0) + (struct.dur ?? 0))) 59 )); 60 this.systemHangSource = ret; 61 if (filter.size > 0 && selectionParam) { 62 this.refreshRowNodeTable(); 63 } 64 }); 65 } 66 67 initElements(): void { 68 this.hangSummaryTable = this.shadowRoot?.querySelector<HTMLDivElement>('#tab-summary'); 69 this.summaryDownLoadTbl = this.shadowRoot?.querySelector<LitTable>('#tb-hang-summary'); 70 this.expansionDiv = this.shadowRoot?.querySelector<HTMLDivElement>('.expansion-div'); 71 this.expansionUpIcon = this.shadowRoot?.querySelector<LitIcon>('.expansion-up-icon'); 72 this.expansionDownIcon = this.shadowRoot?.querySelector<LitIcon>('.expansion-down-icon'); 73 let summaryTreeLevel: string[] = ['Type', '/Process', '/Hang']; 74 this.shadowRoot?.querySelectorAll<HTMLLabelElement>('.head-label').forEach((summaryTreeHead): void => { 75 summaryTreeHead.addEventListener('click', (): void => { 76 this.selectTreeDepth = summaryTreeLevel.indexOf(summaryTreeHead.textContent!); 77 this.expandedNodeList.clear(); 78 this.refreshSelectDepth(this.hangTreeNodes); 79 this.refreshRowNodeTable(true); 80 }); 81 }); 82 this.hangSummaryTable!.onscroll = (): void => { 83 let hangTreeTableEl = this.shadowRoot?.querySelector<HTMLDivElement>('.hang-tree-table'); 84 if (hangTreeTableEl) { 85 hangTreeTableEl.scrollTop = this.hangSummaryTable?.scrollTop || 0; 86 } 87 }; 88 } 89 90 initHtml(): string { 91 return TabPaneHangSummaryHtml; 92 } 93 94 connectedCallback(): void { 95 super.connectedCallback(); 96 new ResizeObserver((): void => { 97 this.parentElement!.style.overflow = 'hidden'; 98 this.refreshRowNodeTable(); 99 }).observe(this.parentElement!); 100 this.expansionDiv?.addEventListener('click', this.expansionClickEvent); 101 } 102 103 disconnectedCallback(): void { 104 super.disconnectedCallback(); 105 this.expansionDiv?.removeEventListener('click', this.expansionClickEvent); 106 } 107 108 expansionClickEvent = (): void => { 109 this.expandedNodeList.clear(); 110 if (this.expansionUpIcon?.name === 'down') { 111 this.selectTreeDepth = 0; 112 this.expansionUpIcon!.name = 'up'; 113 this.expansionDownIcon!.name = 'down'; 114 } else { 115 this.selectTreeDepth = 4; 116 this.expansionUpIcon!.name = 'down'; 117 this.expansionDownIcon!.name = 'up'; 118 } 119 this.refreshSelectDepth(this.hangTreeNodes); 120 this.refreshRowNodeTable(true); 121 }; 122 123 private refreshSelectDepth(hangTreeNodes: HangTreeNode[]): void { 124 hangTreeNodes.forEach((item): void => { 125 if (item.depth < this.selectTreeDepth) { 126 this.expandedNodeList.add(item.id); 127 if (item.children.length > 0) { 128 this.refreshSelectDepth(item.children); 129 } 130 } 131 }); 132 } 133 134 private createRowNodeTableEL( 135 rowNodeList: HangTreeNode[], 136 tableTreeEl: HTMLDivElement, 137 tableCountEl: HTMLDivElement, 138 rowColor: string = '', 139 ): void { 140 let unitPadding: number = 20; 141 let leftPadding: number = 5; 142 rowNodeList.forEach((rowNode): void => { 143 let tableTreeRowEl: HTMLElement = document.createElement('tr'); 144 tableTreeRowEl.className = 'tree-row-tr'; 145 tableTreeRowEl.title = rowNode.name + ''; 146 let leftSpacingEl: HTMLElement = document.createElement('td'); 147 leftSpacingEl.style.paddingLeft = `${rowNode.depth * unitPadding + leftPadding}px`; 148 tableTreeRowEl.appendChild(leftSpacingEl); 149 this.addToggleIconEl(rowNode, tableTreeRowEl); 150 let rowNodeTextEL: HTMLElement = document.createElement('td'); 151 rowNodeTextEL.textContent = rowNode.name + ''; 152 rowNodeTextEL.className = 'row-name-td'; 153 tableTreeRowEl.appendChild(rowNodeTextEL); 154 tableTreeEl.appendChild(tableTreeRowEl); 155 let tableCountRowEl: HTMLElement = document.createElement('tr'); 156 tableCountRowEl.title = rowNode.count.toString(); 157 let countEL: HTMLElement = document.createElement('td'); 158 countEL.textContent = rowNode.count.toString(); 159 countEL.className = 'count-column-td'; 160 if (rowNode.depth === 0) { 161 rowNodeTextEL.style.color = ColorUtils.getHangColor((rowNode.name as HangType) ?? ''); 162 countEL.style.color = ColorUtils.getHangColor((rowNode.name as HangType) ?? ''); 163 } else { 164 rowNodeTextEL.style.color = rowColor; 165 countEL.style.color = rowColor; 166 } 167 tableCountRowEl.appendChild(countEL); 168 tableCountEl.appendChild(tableCountRowEl); 169 if (rowNode.children && this.expandedNodeList.has(rowNode.id)) { 170 this.createRowNodeTableEL(rowNode.children, tableTreeEl, tableCountEl, countEL.style.color); 171 } 172 }); 173 } 174 175 private addToggleIconEl(rowNode: HangTreeNode, tableRowEl: HTMLElement): void { 176 let toggleIconEl: HTMLElement = document.createElement('td'); 177 let expandIcon = document.createElement('lit-icon'); 178 expandIcon.classList.add('tree-icon'); 179 if (rowNode.children && rowNode.children.length > 0) { 180 toggleIconEl.appendChild(expandIcon); 181 // @ts-ignore 182 expandIcon.name = this.expandedNodeList.has(rowNode.id) ? 'minus-square' : 'plus-square'; 183 toggleIconEl.classList.add('expand-icon'); 184 toggleIconEl.addEventListener('click', (): void => { 185 let scrollTop = this.hangSummaryTable?.scrollTop ?? 0; 186 this.changeNode(rowNode.id); 187 this.hangSummaryTable!.scrollTop = scrollTop; 188 }); 189 } 190 tableRowEl.appendChild(toggleIconEl); 191 } 192 193 private changeNode(currentNode: number): void { 194 if (this.expandedNodeList.has(currentNode)) { 195 this.expandedNodeList.delete(currentNode); 196 } else { 197 this.expandedNodeList.add(currentNode); 198 } 199 this.refreshRowNodeTable(true); 200 } 201 202 private refreshRowNodeTable(useCacheRefresh: boolean = false): void { 203 this.hangSummaryTable!.innerHTML = ''; 204 if (this.hangSummaryTable && this.parentElement) { 205 this.hangSummaryTable.style.height = `${this.parentElement!.clientHeight - NUM_30}px`; 206 } 207 if (!useCacheRefresh) { 208 this.hangTreeNodes = this.buildTreeTbhNodes(this.systemHangSource); 209 if (this.hangTreeNodes.length > 0) { 210 this.summaryDownLoadTbl!.recycleDataSource = this.hangTreeNodes; 211 } else { 212 this.summaryDownLoadTbl!.recycleDataSource = []; 213 } 214 } 215 let tableFragmentEl: DocumentFragment = document.createDocumentFragment(); 216 let tableTreeEl: HTMLDivElement = document.createElement('div'); 217 tableTreeEl.className = 'hang-tree-table'; 218 let tableCountEl: HTMLDivElement = document.createElement('div'); 219 if (this.parentElement) { 220 tableTreeEl.style.height = `${this.parentElement!.clientHeight - NUM_40}px`; 221 } 222 this.createRowNodeTableEL(this.hangTreeNodes, tableTreeEl, tableCountEl, ''); 223 let emptyTr = document.createElement('tr'); 224 emptyTr.className = 'tree-row-tr'; 225 tableTreeEl?.appendChild(emptyTr); 226 let emptyCountTr = document.createElement('tr'); 227 emptyCountTr.className = 'tree-row-tr'; 228 tableCountEl?.appendChild(emptyCountTr); 229 tableFragmentEl.appendChild(tableTreeEl); 230 tableFragmentEl.appendChild(tableCountEl); 231 this.hangSummaryTable!.appendChild(tableFragmentEl); 232 } 233 234 private buildTreeTbhNodes(hangTreeNodes: HangStruct[]): HangTreeNode[] { 235 let root: HangTreeNode = { 236 id: 0, depth: 0, children: [], 237 name: 'All', count: 0, 238 }; 239 let id = 1; 240 hangTreeNodes = hangTreeNodes.map(node => ({ 241 ...node, 242 type: SpHangChart.calculateHangType(node.dur ?? 0), 243 })); 244 for (const item of hangTreeNodes) { 245 let typeNode = root.children.find((node) => node.name === item.type); 246 if (typeNode) { 247 typeNode.count += 1; 248 } else { 249 typeNode = { 250 id: id += 1, depth: 0, children: [], count: 1, 251 name: item.type ?? 'Undefined Type?', 252 }; 253 root.children.push(typeNode); 254 } 255 256 let processNode = typeNode.children.find((node) => node.name === item.pname); 257 if (processNode) { 258 processNode.count += 1; 259 } else { 260 processNode = { 261 id: id += 1, depth: 1, children: [], count: 1, 262 name: item.pname ?? 'Process', 263 }; 264 typeNode.children.push(processNode); 265 } 266 267 let contentNode = { 268 id: id += 1, depth: 2, children: [], count: 1, 269 name: item.content ?? '', 270 }; 271 processNode.children.push(contentNode); 272 } 273 274 root.children.sort((a, b) => this.hangLevel.indexOf(b.name) - this.hangLevel.indexOf(a.name)); 275 return root.children; 276 } 277} 278 279export interface HangTreeNode { 280 id: number; 281 depth: number; 282 children: HangTreeNode[]; 283 name: string; 284 count: number; 285}; 286