• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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