• 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 { TraceRow } from '../../base/TraceRow';
19import { TraceSheet } from '../../base/TraceSheet';
20import { Flag } from '../../timer-shaft/Flag';
21import { SpSystemTrace } from '../../../SpSystemTrace';
22import { ns2Timestamp, ns2x, Rect } from '../../../../database/ui-worker/ProcedureWorkerCommon';
23import { ColorUtils } from '../../base/ColorUtils';
24import { LitPageTable } from '../../../../../base-ui/table/LitPageTable';
25import { LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar';
26import { TabPaneHangHtml } from './TabPaneHang.html';
27import { HangStruct } from '../../../../database/ui-worker/ProcedureWorkerHang';
28import { queryAllHangs } from '../../../../database/sql/Hang.sql';
29import { HangType, SpHangChart } from '../../../chart/SpHangChart';
30import { getTimeString } from '../TabPaneCurrentSelection';
31
32/// Hangs 框选Tab页1
33@element('tab-hang')
34export class TabPaneHang extends BaseElement {
35  // Elements
36  private spSystemTrace: SpSystemTrace | undefined | null;
37  private traceSheetEl: TraceSheet | undefined | null;
38  private levelFilterInput: HTMLSelectElement | undefined | null;
39  private searchFilterInput: HTMLInputElement | undefined | null;
40  private processFilter: HTMLInputElement | undefined | null;
41  private hangTableTitle: HTMLDivElement | undefined | null;
42  private hangTbl: LitPageTable | undefined | null;
43
44  private tableTimeHandle: (() => void) | undefined;
45  private tableTitleTimeHandle: (() => void) | undefined;
46  private systemHangSource: HangStructInPane[] = [];
47  private filterData: HangStructInPane[] = [];
48
49  private optionLevel: string[] = ['Instant', 'Circumstantial', 'Micro', 'Severe'];
50  private allowTag: Set<string> = new Set();
51  private progressEL: LitProgressBar | null | undefined;
52  private timeOutId: number | undefined;
53
54  /// 框选时段范围时触发
55  set data(selectionParam: SelectionParam) {
56    if (this.hangTbl) {
57      this.hangTbl.recycleDataSource = [];
58      this.filterData = [];
59    }
60    window.clearTimeout(this.timeOutId);
61    queryAllHangs().then((ret) => {
62      const filter = new Set([...selectionParam.hangMapData.keys()].map(key => key.split(' ').at(-1)));
63      ret = ret.filter(struct => (
64        filter.has(`${struct.pid ?? 0}`) &&
65        ((struct.startTime ?? 0) <= selectionParam.rightNs) &&
66        (selectionParam.leftNs <= ((struct.startTime ?? 0) + (struct.dur ?? 0)))
67      ));
68
69      if (ret.length === 0) {
70        this.progressEL!.loading = false;
71      }
72      this.systemHangSource = ret.map(HangStructInPane.new);
73      this.refreshTable();
74    });
75  }
76
77  init(): void {
78    this.levelFilterInput = this.shadowRoot?.querySelector<HTMLSelectElement>('#level-filter');
79    this.hangTableTitle = this.shadowRoot?.querySelector<HTMLDivElement>('#hang-title');
80    this.searchFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#search-filter');
81    this.processFilter = this.shadowRoot?.querySelector<HTMLInputElement>('#process-filter');
82    this.spSystemTrace = document.querySelector('body > sp-application')?.shadowRoot?.querySelector<SpSystemTrace>('#sp-system-trace');
83    this.tableTimeHandle = this.delayedRefresh(this.refreshTable);
84    this.tableTitleTimeHandle = this.delayedRefresh(this.refreshHangsTitle);
85    this.hangTbl = this.shadowRoot?.querySelector<LitPageTable>('#tb-hang');
86    this.progressEL = this.shadowRoot?.querySelector('.progress') as LitProgressBar;
87    this.hangTbl!.getItemTextColor = (data): string => {
88      const hangData = data as HangStructInPane;
89      return ColorUtils.getHangColor(hangData.type as HangType);
90    };
91    this.hangTbl!.itemTextHandleMap.set('startTime', (startTs) => {
92      // @ts-ignore
93      return ns2Timestamp(startTs);
94    });
95    this.hangTbl!.addEventListener('row-hover', (e): void => {
96      // @ts-ignore
97      let data = e.detail.data as HangStructInPane;
98      if (data) {
99        let pointX: number = ns2x(
100          data.startTime || 0,
101          TraceRow.range!.startNS,
102          TraceRow.range!.endNS,
103          TraceRow.range!.totalNS,
104          new Rect(0, 0, TraceRow.FRAME_WIDTH, 0),
105        );
106        this.traceSheetEl!.systemLogFlag = new Flag(
107          Math.floor(pointX), 0, 0, 0, data.startTime, '#999999', '', true, '',
108        );
109        this.spSystemTrace?.refreshCanvas(false);
110      }
111    });
112    let tbl = this.hangTbl?.shadowRoot?.querySelector<HTMLDivElement>('.table');
113    tbl!.addEventListener('scroll', () => {
114      this.tableTitleTimeHandle?.();
115    });
116    this.hangTbl!.addEventListener('column-click', (evt) => {
117      // @ts-ignore
118      this.sortKey = evt.detail.key;
119      // @ts-ignore
120      this.sortType = evt.detail.sort;
121      // @ts-ignore
122      this.sortByColumn(evt.detail.key, evt.detail.sort);
123      this.refreshHangTab();
124    });
125  }
126
127  initElements(): void {
128    this.init();
129    this.searchFilterInput!.oninput = (): void => {
130      this.tableTimeHandle?.();
131    };
132    this.processFilter!.oninput = (): void => {
133      this.tableTimeHandle?.();
134    };
135    this.levelFilterInput!.onchange = (): void => {
136      this.tableTimeHandle?.();
137    };
138  }
139
140  connectedCallback(): void {
141    super.connectedCallback();
142    new ResizeObserver((): void => {
143      this.parentElement!.style.overflow = 'hidden';
144      if (this.hangTbl) {
145        // @ts-ignore
146        this.hangTbl.shadowRoot.querySelector('.table').style.height =
147          this.parentElement!.clientHeight - 20 - 45 + 'px';
148      }
149      if (this.filterData.length > 0) {
150        this.refreshTable();
151        this.tableTitleTimeHandle?.();
152      }
153    }).observe(this.parentElement!);
154  }
155
156  disconnectedCallback(): void {
157    super.disconnectedCallback();
158  }
159
160  initHtml(): string {
161    return TabPaneHangHtml;
162  }
163
164  refreshHangTab(): void {
165    let tbl = this.hangTbl?.shadowRoot?.querySelector<HTMLDivElement>('.table');
166    let height = 0;
167    if (tbl) {
168      const trs = tbl.querySelectorAll<HTMLElement>('.tr');
169      trs.forEach((trEl: HTMLElement, index: number): void => {
170        if (index === 0) {
171          let frontTotalRowSize = Math.round((tbl!.scrollTop / trEl.clientHeight) * 100) / 100;
172          if (frontTotalRowSize.toString().indexOf('.') >= 0) {
173            let rowCount = frontTotalRowSize.toString().split('.');
174            height += trEl.clientHeight - (Number(rowCount[1]) / 100) * trEl.clientHeight;
175          }
176        }
177        let allTdEl = trEl.querySelectorAll<HTMLElement>('.td');
178        allTdEl[0].style.color = '#3D88C7';
179        allTdEl[0].style.textDecoration = 'underline';
180        allTdEl[0].style.textDecorationColor = '#3D88C7';
181      });
182    }
183  }
184
185  refreshHangsTitle(): void {
186    let tbl = this.hangTbl?.shadowRoot?.querySelector<HTMLDivElement>('.table');
187    let height = 0;
188    let firstRowHeight = 27;
189    let tableHeadHeight = 26;
190    this.refreshHangTab();
191    if (this.hangTbl && this.hangTbl.currentRecycleList.length > 0) {
192      let startDataIndex = this.hangTbl.startSkip + 1;
193      let endDataIndex = startDataIndex;
194      let crossTopHeight = tbl!.scrollTop % firstRowHeight;
195      let topShowHeight = crossTopHeight === 0 ? 0 : firstRowHeight - crossTopHeight;
196      if (topShowHeight < firstRowHeight * 0.3) {
197        startDataIndex++;
198      }
199      let tableHeight = Number(tbl!.style.height.replace('px', '')) - tableHeadHeight;
200      while (height < tableHeight) {
201        if (firstRowHeight <= 0 || height + firstRowHeight > tableHeight) {
202          break;
203        }
204        height += firstRowHeight;
205        endDataIndex++;
206      }
207      if (tableHeight - height - topShowHeight > firstRowHeight * 0.3) {
208        endDataIndex++;
209      }
210      if (endDataIndex >= this.filterData.length) {
211        endDataIndex = this.filterData.length;
212      } else {
213        endDataIndex = this.hangTbl.startSkip === 0 ? endDataIndex - 1 : endDataIndex;
214      }
215      this.hangTableTitle!.textContent = `Hangs [${this.hangTbl.startSkip === 0 ? 1 : startDataIndex},
216        ${endDataIndex}] / ${this.filterData.length || 0}`;
217    } else {
218      this.hangTableTitle!.textContent = 'Hangs [0, 0] / 0';
219    }
220    if (this.hangTbl!.recycleDataSource.length > 0) {
221      this.progressEL!.loading = false;
222    }
223  }
224
225  initTabSheetEl(traceSheet: TraceSheet): void {
226    this.traceSheetEl = traceSheet;
227    this.levelFilterInput!.selectedIndex = 0;
228    this.allowTag.clear();
229    this.processFilter!.value = '';
230    this.searchFilterInput!.value = '';
231  }
232
233  private updateFilterData(): void {
234    if (this.systemHangSource?.length > 0) {
235      this.filterData = this.systemHangSource.filter((data) => this.isFilterHang(data));
236    }
237    if (this.hangTbl) {
238      // @ts-ignore
239      this.hangTbl.shadowRoot.querySelector('.table').style.height = this.parentElement.clientHeight - 20 - 45 + 'px';
240    }
241    if (this.filterData.length > 0) {
242      this.hangTbl!.recycleDataSource = this.filterData;
243    } else {
244      this.hangTbl!.recycleDataSource = [];
245    }
246    this.refreshHangsTitle();
247  }
248
249  private isFilterHang(data: HangStructInPane): boolean {
250    let type = this.levelFilterInput?.selectedIndex ?? 0;
251    let search = this.searchFilterInput?.value.toLocaleLowerCase() ?? '';
252    let process = this.processFilter?.value.toLocaleLowerCase() ?? '';
253    return (
254      (type === 0 || this.optionLevel.indexOf(data.type) >= type) &&
255      (search === '' || data.caller.toLocaleLowerCase().indexOf(search) >= 0) &&
256      (process === '' || data.pname.toLocaleLowerCase().indexOf(process) >= 0)
257    );
258  }
259
260  private refreshTable(): void {
261    if (this.traceSheetEl) {
262      this.traceSheetEl.systemLogFlag = undefined;
263      this.spSystemTrace?.refreshCanvas(false);
264      this.updateFilterData();
265    }
266  }
267
268  private delayedRefresh(optionFn: Function, dur: number = tableTimeOut): () => void {
269    return (...args: []): void => {
270      window.clearTimeout(this.timeOutId);
271      this.timeOutId = window.setTimeout((): void => {
272        optionFn.apply(this, ...args);
273      }, dur);
274    };
275  }
276
277  sortByColumn(key: string, type: number): void {
278    if (type === 0) {
279      this.hangTbl!.recycleDataSource = this.filterData;
280    } else {
281      let arr = Array.from(this.filterData);
282      arr.sort((a, b): number => {
283        if (key === 'startTime') {
284          if (type === 1) {
285            // @ts-ignore
286            return a.startTime - b.startTime;
287          } else {
288            // @ts-ignore
289            return b.startTime - a.startTime;
290          }
291        } else if (key === 'durStr') {
292          if (type === 1) {
293            // @ts-ignore
294            return a.dur - b.dur;
295          } else {
296            // @ts-ignore
297            return b.dur - a.dur;
298          }
299        } else if (key === 'type') {
300          if (type === 1) {
301            // @ts-ignore
302            return a[key].localeCompare(b[key]);
303          } else {
304            // @ts-ignore
305            return b[key].localeCompare(a[key]);
306          }
307        } else if (key === 'pname') {
308          if (type === 1) {
309            // @ts-ignore
310            return a[key].localeCompare(b[key]);
311          } else {
312            // @ts-ignore
313            return b[key].localeCompare(a[key]);
314          }
315        } else if (key === 'sendEventTid') {
316          if (type === 1) {
317            // @ts-ignore
318            return Number(a.sendEventTid) - Number(b.sendEventTid);
319          } else {
320            // @ts-ignore
321            return Number(b.sendEventTid) - Number(a.sendEventTid);
322          }
323        } else if (key === 'sendTime') {
324          if (type === 1) {
325            // @ts-ignore
326            return Number(a.sendTime) - Number(b.sendTime);
327          } else {
328            // @ts-ignore
329            return Number(b.sendTime) - Number(a.sendTime);
330          }
331        } else if (key === 'expectHandleTime') {
332          if (type === 1) {
333            // @ts-ignore
334            return Number(a.expectHandleTime) - Number(b.expectHandleTime);
335          } else {
336            // @ts-ignore
337            return Number(b.expectHandleTime) - Number(a.expectHandleTime);
338          }
339        } else if (key === 'taskNameId') {
340          if (type === 1) {
341            // @ts-ignore
342            return a[key].localeCompare(b[key]);
343          } else {
344            // @ts-ignore
345            return b[key].localeCompare(a[key]);
346          }
347        } else if (key === 'prio') {
348          if (type === 1) {
349            // @ts-ignore
350            return a.prio - b.prio;
351          } else {
352            // @ts-ignore
353            return b.prio - a.prio;
354          }
355        } else if (key === 'caller') {
356          if (type === 1) {
357            // @ts-ignore
358            return a[key].localeCompare(b[key]);
359          } else {
360            // @ts-ignore
361            return b[key].localeCompare(a[key]);
362          }
363        } else {
364          return 0;
365        }
366
367      });
368
369      this.hangTbl!.recycleDataSource = arr;
370    }
371  }
372}
373
374let defaultIndex: number = 1;
375let tableTimeOut: number = 50;
376
377export class HangStructInPane {
378  startTime: number = 0;
379  dur: number = 0;
380  durStr: string = '0';
381  pname: string = 'Process';
382  type: string;
383  sendEventTid: string;
384  sendTime: string;
385  expectHandleTime: string;
386  taskNameId: string;
387  prio: string;
388  caller: string;
389
390  constructor(parent: HangStruct) {
391    this.startTime = parent.startTime ?? this.startTime;
392    this.dur = parent.dur ?? 0;
393    this.durStr = getTimeString(parent.dur ?? 0);
394    this.pname = `${parent.pname ?? this.pname} ${parent.pid ?? ''}`.trim();
395    this.type = SpHangChart.calculateHangType(parent.dur ?? 0);
396    [this.sendEventTid, this.sendTime, this.expectHandleTime, this.taskNameId, this.prio, this.caller] = (parent.content ?? ',0,0,,,').split(',').map(i => i.trim());
397    this.sendEventTid = this.sendEventTid.split(':').at(-1)!;
398  }
399
400  static new(parent: HangStruct): HangStructInPane {
401    return new HangStructInPane(parent);
402  }
403}