• 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 { HiSysEventStruct } from '../../../../database/ui-worker/ProcedureWorkerHiSysEvent';
19import { ns2x, Rect } from '../../../../database/ui-worker/ProcedureWorkerCommon';
20import { LitPageTable } from '../../../../../base-ui/table/LitPageTable';
21import { LitTable } from '../../../../../base-ui/table/lit-table';
22import { LitSlicerTrack } from '../../../../../base-ui/slicer/lit-slicer';
23import { TraceRow } from '../../base/TraceRow';
24import { Flag } from '../../timer-shaft/Flag';
25import { TraceSheet } from '../../base/TraceSheet';
26import { SpSystemTrace } from '../../../SpSystemTrace';
27import { ColorUtils } from '../../base/ColorUtils';
28import { queryHiSysEventTabData } from '../../../../database/sql/Perf.sql';
29import { queryRealTime } from '../../../../database/sql/Clock.sql';
30import { TabPaneHiSysEventsHtml } from './TabPaneHisysEvents.html';
31
32@element('tab-hisysevents')
33export class TabPaneHisysEvents extends BaseElement {
34  private hisysEventSource: Array<HiSysEventStruct> = [];
35  private filterDataList: HiSysEventStruct[] = [];
36  private hiSysEventTable: LitPageTable | undefined | null;
37  private currentSelection: SelectionParam | undefined;
38  private domainFilterInput: HTMLInputElement | undefined | null;
39  private eventNameFilterInput: HTMLInputElement | undefined | null;
40  private levelFilter: HTMLSelectElement | undefined | null;
41  private contentFilterInput: HTMLInputElement | undefined | null;
42  private domainTag: Set<string> = new Set();
43  private domainTagDiv: HTMLDivElement | undefined | null;
44  private eventNameTag: Set<string> = new Set();
45  private eventNameTagDiv: HTMLDivElement | undefined | null;
46  private traceSheetEl: TraceSheet | undefined | null;
47  private spSystemTrace: SpSystemTrace | undefined | null;
48  private detailsTbl: LitTable | null | undefined;
49  private boxDetails: HTMLDivElement | null | undefined;
50  private slicerTrack: LitSlicerTrack | null | undefined;
51  private tableElement: HTMLDivElement | undefined | null;
52  private detailbox: HTMLDivElement | null | undefined;
53  private changeInput: HTMLInputElement | null | undefined;
54  private eventTableTitle: HTMLLabelElement | undefined | null;
55  tableTitleTimeHandle: (() => void) | undefined;
56  private currentDetailList: Array<{ key: string; value: string }> = [];
57  private realTime: number = -1;
58  private bootTime: number = -1;
59  private baseTime: string = '';
60
61  set data(systemEventParam: SelectionParam) {
62    if (systemEventParam === this.currentSelection) {
63      return;
64    }
65    if (this.hiSysEventTable) {
66      this.hiSysEventTable.recycleDataSource = [];
67      this.filterDataList = [];
68    }
69    if (this.detailsTbl) {
70      this.detailsTbl!.recycleDataSource = [];
71    }
72    this.initTabSheetEl();
73    queryRealTime().then((result) => {
74      if (result && result.length > 0) {
75        result.forEach((item) => {
76          if (item.name === 'realtime') {
77            this.realTime = item.ts;
78          } else {
79            this.bootTime = item.ts;
80          }
81        });
82      }
83      queryHiSysEventTabData(systemEventParam.leftNs, systemEventParam.rightNs).then((res) => {
84        this.currentSelection = systemEventParam;
85        systemEventParam.sysAllEventsData = res;
86        this.hiSysEventTable!.recycleDataSource = res;
87        this.hisysEventSource = res;
88        this.updateData();
89      });
90    });
91  }
92
93  queryElements(): void {
94    this.boxDetails = this.shadowRoot?.querySelector<HTMLDivElement>('.box-details');
95    this.hiSysEventTable = this.shadowRoot?.querySelector<LitPageTable>('#tb-hisysevent');
96    this.hiSysEventTable!.getItemTextColor = (data): string => {
97      // @ts-ignore
98      return ColorUtils.getHisysEventColor(data.level);
99    };
100    this.domainTagDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#domainTagFilter');
101    this.eventNameTagDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#eventNameTagFilter');
102    this.domainFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#domain-filter');
103    this.eventNameFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#event-name-filter');
104    this.levelFilter = this.shadowRoot?.querySelector<HTMLSelectElement>('#level-filter');
105    this.spSystemTrace = document
106      .querySelector('body > sp-application')
107      ?.shadowRoot?.querySelector<SpSystemTrace>('#sp-system-trace');
108    this.traceSheetEl = this.spSystemTrace?.shadowRoot?.querySelector('.trace-sheet');
109    this.contentFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#contents-filter');
110    this.changeInput = this.shadowRoot?.querySelector<HTMLInputElement>('#contents-change');
111    this.detailsTbl = this.shadowRoot?.querySelector<LitTable>('#tb-hisysevent-data');
112    this.slicerTrack = this.shadowRoot?.querySelector<LitSlicerTrack>('lit-slicer-track');
113    this.detailbox = this.shadowRoot?.querySelector<HTMLDivElement>('.detail-content');
114    this.tableElement = this.hiSysEventTable?.shadowRoot?.querySelector('.table') as HTMLDivElement;
115    this.eventTableTitle = this.shadowRoot?.querySelector<HTMLLabelElement>('#event-title');
116    this.tableTitleTimeHandle = this.delayedRefresh(this.refreshEventsTitle);
117    this.initHiSysEventListener();
118  }
119
120  /**
121   * 按ns去补0
122   *
123   * @param timestamp
124   * @private
125   */
126  private timestampToNS(timestamp: string): number {
127    return Number(timestamp.toString().padEnd(19, '0'));
128  }
129
130  private initHiSysEventListener(): void {
131    this.hiSysEventTable!.addEventListener('row-click', (event) => {
132      this.changeInput!.value = '';
133      // @ts-ignore
134      const data = event.detail.data;
135      this.convertData(data);
136      this.hiSysEventTable?.clearAllSelection();
137      data.isSelected = true;
138      this.hiSysEventTable?.setCurrentSelection(data);
139      this.updateDetail(this.baseTime);
140    });
141    this.boxDetails!.addEventListener('click', (ev) => {
142      if (ev.target !== this.hiSysEventTable) {
143        this.hiSysEventTable?.clearAllSelection();
144        this.detailsTbl!.dataSource = [];
145        this.boxDetails!.style.width = '100%';
146        this.detailbox!.style.display = 'none';
147        this.slicerTrack!.style.visibility = 'hidden';
148        this.detailsTbl!.style.paddingLeft = '0px';
149      }
150    });
151    this.hiSysEventTable!.addEventListener('row-hover', (e) => {
152      // @ts-ignore
153      let data = e.detail.data;
154      if (data) {
155        this.drawFlag(data.startTs, '#999999');
156      }
157    });
158    this.hiSysEventTable!.addEventListener('column-click', (evt) => {
159      // @ts-ignore
160      this.sortByColumn(evt.detail);
161    });
162    this.tableElement?.addEventListener('mouseout', () => {
163      this.traceSheetEl!.systemLogFlag = undefined;
164      this.spSystemTrace?.refreshCanvas(false);
165    });
166  }
167
168  initElements(): void {
169    this.queryElements();
170    this.detailsTbl!.addEventListener('row-hover', (e) => {
171      // @ts-ignore
172      let data = e.detail.data;
173      if (data && data.key && this.realTime >= 0) {
174        if (data.key.endsWith('_TIME') || data.key.endsWith('_LATENCY')) {
175          this.drawFlag(data.value, '#999999');
176          return;
177        }
178      }
179      this.traceSheetEl!.systemLogFlag = undefined;
180      this.spSystemTrace?.refreshCanvas(false);
181    });
182  }
183
184  private delayedRefresh(optionFn: Function, dur: number = 50): () => void {
185    let timeOutId: number;
186    return (...args: []): void => {
187      window.clearTimeout(timeOutId);
188      timeOutId = window.setTimeout((): void => {
189        optionFn.apply(this, ...args);
190      }, dur);
191    };
192  }
193
194  private refreshEventsTitle(): void {
195    let tbl = this.hiSysEventTable?.shadowRoot?.querySelector<HTMLDivElement>('.table');
196    let height = 0;
197    let firstRowHeight = 27;
198    let tableHeadHeight = 26;
199    if (this.hiSysEventTable && this.hiSysEventTable.currentRecycleList.length > 0) {
200      let startDataIndex = this.hiSysEventTable.startSkip + 1;
201      let endDataIndex = startDataIndex;
202      let crossTopHeight = tbl!.scrollTop % firstRowHeight;
203      let topShowHeight = crossTopHeight === 0 ? 0 : firstRowHeight - crossTopHeight;
204      if (topShowHeight < firstRowHeight * 0.3) {
205        startDataIndex++;
206      }
207      let tableHeight = Number(tbl!.style.height.replace('px', '')) - tableHeadHeight;
208      while (height < tableHeight) {
209        if (firstRowHeight <= 0 || height + firstRowHeight > tableHeight) {
210          break;
211        }
212        height += firstRowHeight;
213        endDataIndex++;
214      }
215      if (tableHeight - height - topShowHeight > firstRowHeight * 0.3) {
216        endDataIndex++;
217      }
218      if (endDataIndex >= this.filterDataList.length) {
219        endDataIndex = this.filterDataList.length;
220      } else {
221        endDataIndex = this.hiSysEventTable.startSkip === 0 ? endDataIndex - 1 : endDataIndex;
222      }
223      this.eventTableTitle!.textContent = `HisysEvents [${this.hiSysEventTable.startSkip === 0 ? 1 : startDataIndex},
224        ${endDataIndex}] / ${this.filterDataList.length || 0}`;
225    } else {
226      this.eventTableTitle!.textContent = 'HisysEvents [0, 0] / 0';
227    }
228  }
229
230  initTabSheetEl(): void {
231    this.levelFilter!.selectedIndex = 0;
232    this.domainFilterInput!.value = '';
233    this.domainTagDiv!.innerHTML = '';
234    this.domainTag.clear();
235    this.eventNameFilterInput!.value = '';
236    this.eventNameTagDiv!.innerHTML = '';
237    this.eventNameTag.clear();
238    this.contentFilterInput!.value = '';
239    this.boxDetails!.style.width = '100%';
240    this.detailbox!.style.display = 'none';
241    this.slicerTrack!.style.visibility = 'hidden';
242    this.detailsTbl!.style.paddingLeft = '0px';
243  }
244
245  initHtml(): string {
246    return TabPaneHiSysEventsHtml;
247  }
248
249  connectedCallback(): void {
250    this.domainFilterInput?.addEventListener('keyup', this.domainKeyEvent);
251    this.eventNameFilterInput?.addEventListener('keyup', this.eventNameKeyEvent);
252    this.contentFilterInput?.addEventListener('input', this.filterInputEvent);
253    this.levelFilter?.addEventListener('change', this.filterInputEvent);
254    this.domainTagDiv?.addEventListener('click', this.domainDivClickEvent);
255    this.eventNameTagDiv?.addEventListener('click', this.eventNameDivClickEvent);
256    this.changeInput?.addEventListener('input', this.changeInputEvent);
257    new ResizeObserver(() => {
258      this.tableElement!.style.height = `${this.parentElement!.clientHeight - 20 - 35}px`;
259      this.hiSysEventTable?.reMeauseHeight();
260      this.detailsTbl!.style.height = `${this.parentElement!.clientHeight - 30}px`;
261      this.parentElement!.style.overflow = 'hidden';
262      this.detailsTbl?.reMeauseHeight();
263      this.updateData();
264      this.tableTitleTimeHandle?.();
265    }).observe(this.parentElement!);
266    let tbl = this.hiSysEventTable?.shadowRoot?.querySelector<HTMLDivElement>('.table');
267    tbl!.addEventListener('scroll', () => {
268      this.tableTitleTimeHandle?.();
269    });
270  }
271
272  disconnectedCallback(): void {
273    super.disconnectedCallback();
274    this.domainFilterInput?.removeEventListener('keyup', this.domainKeyEvent);
275    this.eventNameFilterInput?.removeEventListener('keyup', this.eventNameKeyEvent);
276    this.contentFilterInput?.removeEventListener('input', this.filterInputEvent);
277    this.levelFilter?.addEventListener('change', this.filterInputEvent);
278    this.changeInput?.addEventListener('input', this.changeInputEvent);
279    this.domainTagDiv?.removeEventListener('click', this.domainDivClickEvent);
280    this.eventNameTagDiv?.removeEventListener('click', this.eventNameDivClickEvent);
281  }
282
283  filterInputEvent = (): void => {
284    this.updateData();
285  };
286
287  domainDivClickEvent = (ev: Event): void => {
288    // @ts-ignore
289    let parentNode = ev.target.parentNode;
290    if (parentNode && this.domainTagDiv!.contains(parentNode)) {
291      this.domainTagDiv!.removeChild(parentNode);
292      this.domainTag.delete(parentNode.textContent.trim().toLowerCase());
293    }
294    this.updateData();
295  };
296
297  eventNameDivClickEvent = (ev: Event): void => {
298    // @ts-ignore
299    let parentNode = ev.target.parentNode;
300    if (parentNode && this.eventNameTagDiv!.contains(parentNode)) {
301      this.eventNameTagDiv!.removeChild(parentNode);
302      this.eventNameTag.delete(parentNode.textContent.trim().toLowerCase());
303    }
304    this.updateData();
305  };
306
307  domainKeyEvent = (e: KeyboardEvent): void => {
308    let domainValue = this.domainFilterInput!.value.trim();
309    if (e.key === 'Enter') {
310      if (domainValue !== '' && !this.domainTag.has(domainValue.toLowerCase()) && this.domainTag.size < 10) {
311        let tagElement = this.buildTag(domainValue);
312        this.domainTag.add(domainValue.toLowerCase());
313        this.domainTagDiv!.append(tagElement);
314        this.domainFilterInput!.value = '';
315      }
316    } else if (e.key === 'Backspace') {
317      let index = this.domainTagDiv!.childNodes.length - 1;
318      if (index >= 0 && domainValue === '') {
319        let childNode = this.domainTagDiv!.childNodes[index];
320        this.domainTagDiv!.removeChild(childNode);
321        this.domainTag.delete(childNode.textContent!.trim().toLowerCase());
322      }
323    }
324    this.updateData();
325  };
326
327  private buildTag(domainValue: string): HTMLDivElement {
328    let tagElement = document.createElement('div');
329    tagElement.className = 'tagElement';
330    tagElement.id = domainValue;
331    let tag = document.createElement('div');
332    tag.className = 'tag';
333    tag.innerHTML = domainValue;
334    let closeButton = document.createElement('lit-icon');
335    closeButton.setAttribute('name', 'close-light');
336    closeButton.style.color = '#FFFFFF';
337    tagElement.append(tag);
338    tagElement.append(closeButton);
339    return tagElement;
340  }
341
342  eventNameKeyEvent = (e: KeyboardEvent): void => {
343    let eventNameValue = this.eventNameFilterInput!.value.trim();
344    if (e.key === 'Enter') {
345      if (
346        eventNameValue !== '' &&
347        !this.eventNameTag.has(eventNameValue.toLowerCase()) &&
348        this.eventNameTag.size < 10
349      ) {
350        let tagElement = this.buildTag(eventNameValue);
351        this.eventNameTag!.add(eventNameValue.toLowerCase());
352        this.eventNameTagDiv!.append(tagElement);
353        this.eventNameFilterInput!.value = '';
354      }
355    } else if (e.key === 'Backspace') {
356      let index = this.eventNameTagDiv!.childNodes.length - 1;
357      if (index >= 0 && eventNameValue === '') {
358        let childNode = this.eventNameTagDiv!.childNodes[index];
359        this.eventNameTagDiv!.removeChild(childNode);
360        this.eventNameTag.delete(childNode.textContent!.trim().toLowerCase());
361      }
362    }
363    this.updateData();
364  };
365
366  updateData(): void {
367    if (this.hisysEventSource.length > 0) {
368      this.filterDataList = this.hisysEventSource.filter((data) => this.filterData(data));
369    }
370    if (this.filterDataList.length > 0) {
371      this.hiSysEventTable!.recycleDataSource = this.filterDataList;
372    } else {
373      this.hiSysEventTable!.recycleDataSource = [];
374    }
375    this.refreshEventsTitle();
376  }
377
378  filterData(data: HiSysEventStruct): boolean {
379    let level = this.levelFilter?.value;
380    let contentsValue = this.contentFilterInput?.value.toLowerCase() || '';
381    contentsValue = contentsValue.replace(/\s/g, '');
382    return (
383      (level === 'ALL' || data.level! === level) &&
384      (this.domainTag.size === 0 || this.domainTag.has(data.domain!.toLowerCase())) &&
385      (this.eventNameTag.size === 0 || this.eventNameTag.has(data.eventName!.toLowerCase())) &&
386      (contentsValue === '' || data.contents!.toLowerCase().replace(/\s/g, '').indexOf(contentsValue) >= 0)
387    );
388  }
389
390  changeInputEvent = (): void => {
391    const changeValue = this.changeInput!.value;
392    const currentValue = changeValue;
393    if (!/^[0-9]*$/.test(changeValue) || isNaN(Number(currentValue))) {
394      this.changeInput!.value = '';
395      this.updateDetail(this.baseTime);
396    } else {
397      this.updateDetail(this.changeInput!.value);
398    }
399  };
400
401  updateDetail(baseTime: string): void {
402    const latencySuffix = '_LATENCY';
403    let detailList: Array<{ key: string; value: string }> = [];
404    this.currentDetailList.forEach((item) => {
405      const latencyValue = item.key && item.key.endsWith(latencySuffix) ? item.value + Number(baseTime) : item.value;
406      detailList.push({
407        key: item.key,
408        value: latencyValue,
409      });
410    });
411    this.detailsTbl!.recycleDataSource = detailList;
412  }
413
414  convertData = (data: HiSysEventStruct): void => {
415    this.baseTime = '';
416    this.currentDetailList = [
417      {
418        key: 'key',
419        value: 'value',
420      },
421    ];
422    const content = JSON.parse(data.contents ?? '{}');
423    if (content && typeof content === 'object') {
424      let isFirstTime = true;
425      let keyList = Object.keys(content);
426      keyList.forEach((key) => {
427        const value: string = content[key];
428        let contentValue = value;
429        if (key.endsWith('_TIME')) {
430          if (!isNaN(Number(value))) {
431            contentValue = (this.timestampToNS(value) - this.realTime + this.bootTime).toString();
432            if (this.realTime < 0) {
433              contentValue = value;
434            }
435            if (isFirstTime) {
436              this.baseTime = contentValue;
437              isFirstTime = false;
438            }
439          }
440          if (key === 'INPUT_TIME') {
441            this.baseTime = contentValue;
442            isFirstTime = false;
443          }
444        }
445        this.currentDetailList.push({
446          key: key,
447          value: contentValue,
448        });
449      });
450    }
451    this.changeInput!.value = `${this.baseTime}`;
452    this.detailsTbl!.recycleDataSource = this.currentDetailList;
453    this.slicerTrack!.style.visibility = 'visible';
454    this.detailsTbl!.style.paddingLeft = '20px';
455    this.boxDetails!.style.width = '65%';
456    this.detailbox!.style.display = 'block';
457  };
458
459  sortByColumn(framesDetail: { sort: number; key: string }): void {
460    let compare = function (property: string, sort: number, type: string) {
461      return function (eventLeftData: HiSysEventStruct, eventRightData: HiSysEventStruct): number {
462        let firstSortNumber: number = -1;
463        let SecondSortNumber: number = 1;
464        let thirdSortNumber: number = 2;
465        // @ts-ignore
466        let rightEventData = eventRightData[property];
467        // @ts-ignore
468        let leftEventData = eventLeftData[property];
469        if (type === 'number') {
470          return sort === thirdSortNumber
471            ? parseFloat(rightEventData) - parseFloat(leftEventData)
472            : parseFloat(leftEventData) - parseFloat(rightEventData);
473        } else {
474          if (rightEventData > leftEventData) {
475            return sort === thirdSortNumber ? SecondSortNumber : firstSortNumber;
476          } else {
477            if (rightEventData === leftEventData) {
478              return 0;
479            } else {
480              return sort === thirdSortNumber ? firstSortNumber : SecondSortNumber;
481            }
482          }
483        }
484      };
485    };
486    this.hisysEventSource.sort(compare(framesDetail.key, framesDetail.sort, 'number'));
487    this.hiSysEventTable!.recycleDataSource = this.hisysEventSource;
488  }
489
490  drawFlag(value: number, color: string): void {
491    let pointX: number = ns2x(
492      value || 0,
493      TraceRow.range!.startNS,
494      TraceRow.range!.endNS,
495      TraceRow.range!.totalNS,
496      new Rect(0, 0, TraceRow.FRAME_WIDTH, 0)
497    );
498    this.traceSheetEl!.systemLogFlag = new Flag(Math.floor(pointX), 0, 0, 0, value!, color, '', true, '');
499    this.spSystemTrace?.refreshCanvas(false);
500  }
501}
502
503const millisecond = 1000_000;
504