• 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 '../base-ui/menu/LitMainMenu';
18import '../base-ui/icon/LitIcon';
19import '../base-ui/loading/LitLoading';
20import '../base-ui/like/LitLike';
21import { SpMetrics } from './component/SpMetrics';
22import { SpHelp } from './component/SpHelp';
23import './component/SpHelp';
24import { SpQuerySQL } from './component/SpQuerySQL';
25import './component/SpQuerySQL';
26import { SpSystemTrace } from './component/SpSystemTrace';
27import { LitMainMenu, MenuGroup, MenuItem } from '../base-ui/menu/LitMainMenu';
28import { SpInfoAndStats } from './component/SpInfoAndStas';
29import '../base-ui/progress-bar/LitProgressBar';
30import { LitProgressBar } from '../base-ui/progress-bar/LitProgressBar';
31import { SpRecordTrace } from './component/SpRecordTrace';
32import { SpWelcomePage } from './component/SpWelcomePage';
33import { LitSearch } from './component/trace/search/Search';
34import {
35  getThreadPoolTraceBuffer,
36  getThreadPoolTraceBufferCacheKey,
37  setThreadPoolTraceBuffer,
38  threadPool,
39  threadPool2,
40} from './database/SqlLite';
41import './component/trace/search/Search';
42import './component/SpWelcomePage';
43import './component/SpSystemTrace';
44import './component/SpRecordTrace';
45import './component/SpMetrics';
46import './component/SpInfoAndStas';
47import './component/trace/base/TraceRow';
48import './component/schedulingAnalysis/SpSchedulingAnalysis';
49import { error, info, log } from '../log/Log';
50import { LitMainMenuGroup } from '../base-ui/menu/LitMainMenuGroup';
51import { LitMainMenuItem } from '../base-ui/menu/LitMainMenuItem';
52import { LitIcon } from '../base-ui/icon/LitIcon';
53import { TraceRow } from './component/trace/base/TraceRow';
54import { SpSchedulingAnalysis } from './component/schedulingAnalysis/SpSchedulingAnalysis';
55import './component/trace/base/TraceRowConfig';
56import { TraceRowConfig } from './component/trace/base/TraceRowConfig';
57import { ColorUtils } from './component/trace/base/ColorUtils';
58import { SpStatisticsHttpUtil } from '../statistics/util/SpStatisticsHttpUtil';
59import { FlagsConfig, SpFlags } from './component/SpFlags';
60import './component/SpFlags';
61import './component/trace/base/CustomThemeColor';
62import { CustomThemeColor, Theme } from './component/trace/base/CustomThemeColor';
63import { convertPool } from './database/Convert';
64import { LongTraceDBUtils } from './database/LongTraceDBUtils';
65import { type SpKeyboard } from './component/SpKeyboard';
66import './component/SpKeyboard';
67import { parseKeyPathJson } from './component/Utils';
68import { Utils } from './component/trace/base/Utils';
69import {
70  applicationHtml,
71  clearTraceFileCache,
72  findFreeSizeAlgorithm,
73  getCurrentDataTime,
74  indexedDataToBufferData,
75  isZipFile,
76  isZlibFile,
77  postLog,
78  readTraceFileBuffer,
79  TraceMode,
80} from './SpApplicationPublicFunc';
81import { queryExistFtrace } from './database/sql/SqlLite.sql';
82import '../base-ui/chart/scatter/LitChartScatter';
83import { SpThirdParty } from './component/SpThirdParty';
84import './component/SpThirdParty';
85import { cancelCurrentTraceRowHighlight } from './component/SpSystemTrace.init';
86import './component/SpBubblesAI';
87import './component/SpAiAnalysisPage';
88import { WebSocketManager } from '../webSocket/WebSocketManager';
89import { SpAiAnalysisPage } from './component/SpAiAnalysisPage';
90import './component/SpAdvertisement';
91import { ShadowRootInput } from './component/trace/base/ShadowRootInput';
92import { SpBubblesAI } from './component/SpBubblesAI';
93
94@element('sp-application')
95export class SpApplication extends BaseElement {
96  private static loadingProgress: number = 0;
97  private static progressStep: number = 2;
98  longTraceHeadMessageList: Array<{
99    pageNum: number;
100    data: ArrayBuffer;
101  }> = [];
102
103  longTraceDataList: Array<{
104    fileType: string;
105    index: number;
106    pageNum: number;
107    startOffsetSize: number;
108    endOffsetSize: number;
109  }> = [];
110
111  longTraceTypeMessageMap:
112    | Map<
113      number,
114      Array<{
115        fileType: string;
116        startIndex: number;
117        endIndex: number;
118        size: number;
119      }>
120    >
121    | undefined
122    | null;
123  static skinChange: Function | null | undefined = null;
124  static skinChange2: Function | null | undefined = null;
125  static isTraceLoaded: Boolean = false;
126  skinChangeArray: Array<Function> = [];
127  private rootEL: HTMLDivElement | undefined | null;
128  private headerDiv: HTMLDivElement | undefined | null;
129  private spWelcomePage: SpWelcomePage | undefined | null;
130  private spMetrics: SpMetrics | undefined | null;
131  private spQuerySQL: SpQuerySQL | undefined | null;
132  private spInfoAndStats: SpInfoAndStats | undefined | null;
133  private spSystemTrace: SpSystemTrace | undefined | null;
134  private spHelp: SpHelp | undefined | null;
135  private spKeyboard: SpKeyboard | undefined | null;
136  private spFlags: SpFlags | undefined | null;
137  private spRecordTrace: SpRecordTrace | undefined | null;
138  private spSchedulingAnalysis: SpSchedulingAnalysis | undefined | null;
139  private mainMenu: LitMainMenu | undefined | null;
140  private menu: HTMLDivElement | undefined | null;
141  private progressEL: LitProgressBar | undefined | null;
142  private litSearch: LitSearch | undefined | null;
143  private litRecordSearch: LitSearch | undefined | null;
144  private sidebarButton: HTMLDivElement | undefined | null;
145  private chartFilter: TraceRowConfig | undefined | null;
146  private cutTraceFile: HTMLImageElement | undefined | null;
147  private exportRecord: LitIcon | undefined | null;
148  private longTracePage: HTMLDivElement | undefined | null;
149  private customColor: CustomThemeColor | undefined | null;
150  private filterConfig: LitIcon | undefined | null;
151  private configClose: LitIcon | undefined | null;
152  private spThirdParty: SpThirdParty | undefined | null;
153  // 关键路径标识
154  private importConfigDiv: HTMLInputElement | undefined | null;
155  private closeKeyPath: HTMLDivElement | undefined | null;
156  private importFileBt: HTMLInputElement | undefined | null;
157  private contentLeftOption: HTMLDivElement | undefined | null;
158  private contentCenterOption: HTMLDivElement | undefined | null;
159  private contentRightOption: HTMLDivElement | undefined | null;
160  private childComponent: Array<unknown> | undefined | null;
161  private spAiAnalysisPage: SpAiAnalysisPage | undefined | null;
162  private aiAnalysis: HTMLImageElement | undefined | null;
163  private keyCodeMap = {
164    61: true,
165    107: true,
166    109: true,
167    173: true,
168    187: true,
169    189: true,
170  };
171  private traceFileName: string | undefined;
172  private markJson: string | undefined;
173  colorTransiton?: NodeJS.Timeout;
174  static isLongTrace: boolean = false;
175  fileTypeList: string[] = ['ebpf', 'arkts', 'hiperf'];
176  private pageTimStamp: number = 0;
177  private currentPageNum: number = 1;
178  private currentDataTime: string[] = [];
179  static traceType: String = '';
180  private isZipFile: boolean = false;
181
182  static get observedAttributes(): Array<string> {
183    return ['server', 'sqlite', 'wasm', 'dark', 'vs', 'query-sql', 'subsection'];
184  }
185
186  get dark(): boolean {
187    return this.hasAttribute('dark');
188  }
189
190  set dark(value) {
191    if (value) {
192      this.rootEL!.classList.add('dark');
193      this.setAttribute('dark', '');
194    } else {
195      this.rootEL!.classList.remove('dark');
196      this.removeAttribute('dark');
197    }
198    if (this.skinChangeArray.length > 0) {
199      this.skinChangeArray.forEach((item) => item(value));
200    }
201    if (SpApplication.skinChange) {
202      SpApplication.skinChange(value);
203    }
204    if (SpApplication.skinChange2) {
205      SpApplication.skinChange2(value);
206    }
207
208    if (this.spHelp) {
209      this.spHelp.dark = value;
210    }
211  }
212
213  get sqlite(): boolean {
214    return this.hasAttribute('sqlite');
215  }
216
217  get wasm(): boolean {
218    return this.hasAttribute('wasm');
219  }
220
221  set wasm(isWasm: boolean) {
222    if (isWasm) {
223      this.setAttribute('wasm', '');
224    } else {
225      this.hasAttribute('wasm') && this.removeAttribute('wasm');
226    }
227  }
228
229  get server(): boolean {
230    return this.hasAttribute('server');
231  }
232
233  set server(s: boolean) {
234    if (s) {
235      this.setAttribute('server', '');
236    } else {
237      this.removeAttribute('server');
238    }
239  }
240
241  get querySql(): boolean {
242    return this.hasAttribute('query-sql');
243  }
244
245  set querySql(isShowMetric) {
246    if (isShowMetric) {
247      this.setAttribute('query-sql', '');
248    } else {
249      this.removeAttribute('query-sql');
250    }
251  }
252
253  set search(search: boolean) {
254    if (search) {
255      this.setAttribute('search', '');
256    } else {
257      this.removeAttribute('search');
258    }
259  }
260
261  get search(): boolean {
262    return this.hasAttribute('search');
263  }
264
265  addSkinListener(handler: Function): void {
266    this.skinChangeArray.push(handler);
267  }
268
269  removeSkinListener(handler: Function): void {
270    this.skinChangeArray.splice(this.skinChangeArray.indexOf(handler), 1);
271  }
272
273  initHtml(): string {
274    return applicationHtml;
275  }
276
277  initPlugin(): void {
278    SpStatisticsHttpUtil.initStatisticsServerConfig();
279    SpStatisticsHttpUtil.addUserVisitAction('visit');
280    LongTraceDBUtils.getInstance().createDBAndTable().then();
281  }
282
283  initElements(): void {
284    this.wasm = true;
285    this.initPlugin();
286    this.querySql = true;
287    this.rootEL = this.shadowRoot!.querySelector<HTMLDivElement>('.root');
288    this.headerDiv = this.shadowRoot!.querySelector<HTMLDivElement>('.search-vessel');
289    this.spWelcomePage = this.shadowRoot!.querySelector('#sp-welcome') as SpWelcomePage;
290    this.spMetrics = this.shadowRoot!.querySelector<SpMetrics>('#sp-metrics') as SpMetrics; // new SpMetrics();
291    this.spQuerySQL = this.shadowRoot!.querySelector<SpQuerySQL>('#sp-query-sql') as SpQuerySQL; // new SpQuerySQL();
292    this.spInfoAndStats = this.shadowRoot!.querySelector<SpInfoAndStats>('#sp-info-and-stats'); // new SpInfoAndStats();
293    this.spSystemTrace = this.shadowRoot!.querySelector<SpSystemTrace>('#sp-system-trace');
294    this.spHelp = this.shadowRoot!.querySelector<SpHelp>('#sp-help');
295    this.spKeyboard = this.shadowRoot!.querySelector<SpKeyboard>('#sp-keyboard') as SpKeyboard;
296    this.spFlags = this.shadowRoot!.querySelector<SpFlags>('#sp-flags') as SpFlags;
297    this.spRecordTrace = this.shadowRoot!.querySelector<SpRecordTrace>('#sp-record-trace');
298    this.spSchedulingAnalysis = this.shadowRoot!.querySelector<SpSchedulingAnalysis>('#sp-scheduling-analysis');
299    this.mainMenu = this.shadowRoot?.querySelector('#main-menu') as LitMainMenu;
300    this.menu = this.mainMenu.shadowRoot?.querySelector('.menu-button') as HTMLDivElement;
301    this.progressEL = this.shadowRoot?.querySelector('.progress') as LitProgressBar;
302    this.litSearch = this.shadowRoot?.querySelector('#lit-search') as LitSearch;
303    this.litRecordSearch = this.shadowRoot?.querySelector('#lit-record-search') as LitSearch;
304    this.sidebarButton = this.shadowRoot?.querySelector('.sidebar-button');
305    this.chartFilter = this.shadowRoot?.querySelector('.chart-filter') as TraceRowConfig;
306    this.aiAnalysis = this.shadowRoot?.querySelector('.ai_analysis') as HTMLImageElement;
307    this.cutTraceFile = this.shadowRoot?.querySelector('.cut-trace-file') as HTMLImageElement;
308    this.exportRecord = this.shadowRoot?.querySelector('.export-record') as LitIcon;
309    this.longTracePage = this.shadowRoot!.querySelector('.long_trace_page') as HTMLDivElement;
310    this.customColor = this.shadowRoot?.querySelector('.custom-color') as CustomThemeColor;
311    this.filterConfig = this.shadowRoot?.querySelector('.filter-config') as LitIcon;
312    this.spThirdParty = this.shadowRoot!.querySelector('#sp-third-party') as SpThirdParty;
313    this.configClose = this.shadowRoot
314      ?.querySelector<HTMLElement>('.chart-filter')!
315      .shadowRoot?.querySelector<LitIcon>('.config-close');
316    this.importConfigDiv = this.shadowRoot?.querySelector<HTMLInputElement>('#import-key-path');
317    this.closeKeyPath = this.shadowRoot?.querySelector<HTMLDivElement>('#close-key-path');
318    this.importFileBt = this.shadowRoot?.querySelector<HTMLInputElement>('#import-config');
319    this.contentRightOption = this.shadowRoot?.querySelector<HTMLDivElement>('.content-right-option');
320    this.contentLeftOption = this.shadowRoot?.querySelector<HTMLDivElement>('.content-left-option');
321    this.contentCenterOption = this.shadowRoot?.querySelector<HTMLDivElement>('.content-center-option');
322    this.spAiAnalysisPage = this.shadowRoot!.querySelector('#sp-ai-analysis') as SpAiAnalysisPage;
323    let xiaoLubanEl: HTMLElement | null = this.shadowRoot!.querySelector<HTMLElement>('#sp-bubbles');
324    this.initElementsAttr();
325    this.initEvents();
326    this.initRecordEvents();
327    this.initCustomEvents();
328    this.initCustomColorHandler();
329    this.initImportConfigEvent();
330    this.initSlideMenuEvents();
331    this.resetMenus();
332    this.initGlobalEvents();
333    this.initDocumentListener();
334    this.initElementsEnd();
335    this.dragXiaolubanEvents(xiaoLubanEl!);
336    this.connectWebSocket();
337
338  }
339  private dragXiaolubanEvents(xiaoLubanEl: HTMLElement): void {
340    document.querySelector('body')!.addEventListener('dragover', function (event) {
341      event.preventDefault();
342      event.stopPropagation();
343    });
344    document.querySelector('body')!.addEventListener('drop', function (event) {
345      event.preventDefault();
346      event.stopPropagation();
347      if (!SpBubblesAI.isAIHover) {
348        return;
349      };
350      const x = event.clientX - 15 < 0 ? 0 : event.clientX - 15;
351      const y = event.clientY - 15 < 0 ? 0 : event.clientY - 15;
352      xiaoLubanEl!.style.left = `${x}px`;
353      xiaoLubanEl!.style.top = `${y}px`;
354    });
355  }
356  private connectWebSocket(): void {
357    document.addEventListener('DOMContentLoaded', function () {//
358      WebSocketManager.getInstance();
359    });
360  }
361  private initElementsEnd(): void {
362    let urlParams = new URL(window.location.href).searchParams;
363    let jsonStr = '';
364    if (urlParams && urlParams.get('json')) {
365      jsonStr = decodeURIComponent(window.location.href.split('&').reverse()[0].split('=')[1]);
366    }
367    if (urlParams && urlParams.get('trace') && urlParams.get('link')) {
368      this.openLineFileHandler(urlParams, jsonStr);
369    } else if (urlParams && urlParams.get('action')) {
370      this.helpClick(urlParams!);
371    } else {
372      this.openMenu(true);
373    }
374  }
375
376  private helpClick(urlParams: URLSearchParams): void {
377    if (urlParams.get('action') === 'help') {
378      SpStatisticsHttpUtil.addOrdinaryVisitAction({
379        event: 'help_page',
380        action: 'help_doc',
381      });
382      this.spHelp!.dark = this.dark;
383      this.showContent(this.spHelp!);
384    } else if (urlParams.get('action')!.length > 4) {
385      this.showContent(this.spHelp!);
386    }
387  }
388
389  private initElementsAttr(): void {
390    this.mainMenu!.setAttribute('main_menu', '1');
391    this.chartFilter!.setAttribute('mode', '');
392    this.chartFilter!.setAttribute('hidden', '');
393    this.customColor!.setAttribute('mode', '');
394    this.customColor!.setAttribute('hidden', '');
395    this.childComponent = [
396      this.spSystemTrace,
397      this.spRecordTrace,
398      this.spWelcomePage,
399      this.spAiAnalysisPage,
400      this.spMetrics,
401      this.spQuerySQL,
402      this.spSchedulingAnalysis,
403      this.spInfoAndStats,
404      this.spHelp,
405      this.spFlags,
406      this.spKeyboard,
407      this.spThirdParty,
408    ];
409  }
410
411  private openLongTraceFile(ev: CustomEvent, isRecordTrace: boolean = false): void {
412    this.returnOriginalUrl();
413    this.wasm = true;
414    this.openFileInit(true);
415    let detail = ev.detail;
416    let initRes = this.longTraceFileInit(isRecordTrace, detail);
417    if (!isRecordTrace && initRes) {
418      let readSize = 0;
419      let timStamp = new Date().getTime();
420      const { traceTypePage, allFileSize, normalTraceNames, specialTraceNames } = initRes;
421      if (normalTraceNames.length <= 0) {
422        return;
423      }
424      const readFiles = async (
425        files: FileList,
426        traceTypePage: Array<number>,
427        normalNames: Array<string>,
428        specialNames: Array<string>
429      ): Promise<unknown> => {
430        const promises = Array.from(files).map((file) => {
431          if (normalNames.indexOf(file.name.toLowerCase()) >= 0) {
432            return this.longTraceFileRead(file, true, traceTypePage, readSize, timStamp, allFileSize);
433          } else if (specialNames.indexOf(file.name.toLowerCase()) >= 0) {
434            return this.longTraceFileRead(file, false, traceTypePage, readSize, timStamp, allFileSize);
435          } else {
436            return;
437          }
438        });
439        return Promise.all(promises);
440      };
441      this.litSearch!.setPercent('Read in file: ', 1);
442      readFiles(detail, traceTypePage, normalTraceNames, specialTraceNames).then(() => {
443        this.litSearch!.setPercent('Cut in file: ', 1);
444        this.sendCutFileMessage(timStamp);
445      });
446    }
447  }
448
449  private longTraceFileRead = async (
450    file: File,
451    isNormalType: boolean,
452    traceTypePage: Array<number>,
453    readSize: number,
454    timStamp: number,
455    allFileSize: number
456  ): Promise<boolean> => {
457    info('reading long trace file ', file.name);
458    return new Promise((resolve, reject) => {
459      let fr = new FileReader();
460      let message = { fileType: '', startIndex: 0, endIndex: 0, size: 0 };
461      info('Parse long trace using wasm mode ');
462      const { fileType, pageNumber } = this.getFileTypeAndPages(file.name, isNormalType, traceTypePage);
463      let chunk = 48 * 1024 * 1024;
464      let offset = 0;
465      let sliceLen = 0;
466      let index = 1;
467      fr.onload = (): void => {
468        let data = fr.result as ArrayBuffer;
469        LongTraceDBUtils.getInstance()
470          .addLongTableData(data, fileType, timStamp, pageNumber, index, offset, sliceLen)
471          .then(() => {
472            this.longTraceFileReadMessagePush(index, isNormalType, pageNumber, offset, sliceLen, fileType, data);
473            offset += sliceLen;
474            if (offset < file.size) {
475              index++;
476            }
477            continueReading();
478          });
479      };
480      const continueReading = (): void => {
481        if (offset >= file.size) {
482          message.endIndex = index;
483          message.size = file.size;
484          this.longTraceFileReadMessageHandler(pageNumber, message);
485          resolve(true);
486          return;
487        }
488        if (index === 1) {
489          message.fileType = fileType;
490          message.startIndex = index;
491        }
492        sliceLen = Math.min(file.size - offset, chunk);
493        let slice = file.slice(offset, offset + sliceLen);
494        readSize += slice.size;
495        let percentValue = ((readSize * 100) / allFileSize).toFixed(2);
496        this.litSearch!.setPercent('Read in file: ', Number(percentValue));
497        fr.readAsArrayBuffer(slice);
498      };
499      continueReading();
500      fr.onerror = (): void => reject(false);
501      info('read over long trace file ', file.name);
502    });
503  };
504
505  getFileTypeAndPages(
506    fileName: string,
507    isNormalType: boolean,
508    traceTypePage: Array<number>
509  ): {
510    fileType: string;
511    pageNumber: number;
512  } {
513    let fileType = 'trace';
514    let pageNumber = 0;
515    let firstLastIndexOf = fileName.lastIndexOf('.');
516    let firstText = fileName.slice(0, firstLastIndexOf);
517    let resultLastIndexOf = firstText.lastIndexOf('_');
518    let searchResult = firstText.slice(resultLastIndexOf + 1, firstText.length);
519    if (isNormalType) {
520      pageNumber = traceTypePage.lastIndexOf(Number(searchResult));
521    } else {
522      fileType = searchResult;
523    }
524    return { fileType, pageNumber };
525  }
526
527  private longTraceFileInit(
528    isRecordTrace: boolean,
529    detail: unknown
530  ):
531    | {
532      traceTypePage: number[];
533      allFileSize: number;
534      normalTraceNames: string[];
535      specialTraceNames: string[];
536    }
537    | undefined {
538    if (!this.wasm) {
539      this.progressEL!.loading = false;
540      return;
541    }
542    if (this.longTracePage) {
543      this.longTracePage.style.display = 'none';
544      this.litSearch!.style.marginLeft = '0px';
545      this.shadowRoot!.querySelector('.page-number-list')!.innerHTML = '';
546    }
547    this.currentPageNum = 1;
548    if (isRecordTrace) {
549      //@ts-ignore
550      this.sendCutFileMessage(detail.timeStamp);
551      return undefined;
552    } else {
553      this.longTraceHeadMessageList = [];
554      this.longTraceTypeMessageMap = undefined;
555      this.longTraceDataList = [];
556      let traceTypePage: Array<number> = [];
557      let allFileSize = 0;
558      let normalTraceNames: Array<string> = [];
559      let specialTraceNames: Array<string> = []; //@ts-ignore
560      for (let index = 0; index < detail.length; index++) {
561        //@ts-ignore
562        let file = detail[index];
563        let fileName = file.name as string;
564        allFileSize += file.size;
565        let specialMatch = fileName.match(/_(arkts|ebpf|hiperf)\.htrace$/);
566        let normalMatch = fileName.match(/_\d{8}_\d{6}_\d+\.htrace$/);
567        if (normalMatch) {
568          normalTraceNames.push(fileName);
569          let fileNameStr = fileName.split('.')[0];
570          let pageMatch = fileNameStr.match(/\d+$/);
571          if (pageMatch) {
572            traceTypePage.push(Number(pageMatch[0]));
573          }
574        } else if (specialMatch) {
575          specialTraceNames.push(fileName);
576        }
577      }
578      if (normalTraceNames.length <= 0) {
579        this.traceFileLoadFailedHandler('No large trace files exists in the folder!');
580      }
581      traceTypePage.sort((leftNum: number, rightNum: number) => leftNum - rightNum);
582      return { traceTypePage, allFileSize, normalTraceNames, specialTraceNames };
583    }
584  }
585
586  longTraceFileReadMessagePush(
587    index: number,
588    isNormalType: boolean,
589    pageNumber: number,
590    offset: number,
591    sliceLen: number,
592    fileType: string,
593    data: ArrayBuffer
594  ): void {
595    if (index === 1 && isNormalType) {
596      this.longTraceHeadMessageList.push({
597        pageNum: pageNumber,
598        data: data.slice(offset, 1024),
599      });
600    }
601    this.longTraceDataList.push({
602      index: index,
603      fileType: fileType,
604      pageNum: pageNumber,
605      startOffsetSize: offset,
606      endOffsetSize: offset + sliceLen,
607    });
608  }
609
610  longTraceFileReadMessageHandler(pageNumber: number, message: unknown): void {
611    if (this.longTraceTypeMessageMap) {
612      if (this.longTraceTypeMessageMap?.has(pageNumber)) {
613        let oldTypeList = this.longTraceTypeMessageMap?.get(pageNumber);
614        //@ts-ignore
615        oldTypeList?.push(message);
616        this.longTraceTypeMessageMap?.set(pageNumber, oldTypeList!);
617      } else {
618        //@ts-ignore
619        this.longTraceTypeMessageMap?.set(pageNumber, [message]);
620      }
621    } else {
622      this.longTraceTypeMessageMap = new Map();
623      //@ts-ignore
624      this.longTraceTypeMessageMap.set(pageNumber, [message]);
625    }
626  }
627
628  private openTraceFile(ev: File): void {
629    SpApplication.isTraceLoaded = false;
630    this.returnOriginalUrl();
631    this.removeAttribute('custom-color');
632    this.chartFilter!.setAttribute('hidden', '');
633    this.customColor!.setAttribute('hidden', '');
634    this.longTracePage!.style.display = 'none';
635    this.litSearch!.style.marginLeft = '0px';
636    // 诊断的db标记重置
637    SpAiAnalysisPage.isRepeatedly = false;
638    this.spAiAnalysisPage!.style.display = 'none';
639    let pageListDiv = this.shadowRoot?.querySelector('.page-number-list') as HTMLDivElement;
640    pageListDiv.innerHTML = '';
641    this.openFileInit();
642    if (this.importConfigDiv && this.closeKeyPath) {
643      this.importConfigDiv.style.display = 'none';
644      this.closeKeyPath.style.display = 'none';
645    }
646    let fileName = ev.name;
647    this.traceFileName = fileName;
648    let showFileName = fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.'));
649    window.sessionStorage.setItem('fileName', showFileName);
650    TraceRow.rangeSelectObject = undefined;
651    let typeStr = ev.slice(0, 100);
652    let reader: FileReader | null = new FileReader();
653    reader.readAsText(typeStr);
654    reader.onloadend = (event): void => {
655      let isIncludeMark = `${reader?.result}`.includes('MarkPositionJSON');
656      let typeHeader;
657      if (isIncludeMark) {
658        let markLength = `${reader?.result}`.split('->')[0].replace('MarkPositionJSON', '');
659        typeHeader = ev.slice(markLength.length + parseInt(markLength), markLength.length + parseInt(markLength) + 6);
660      } else {
661        typeHeader = ev.slice(0, 6);
662      }
663      this.judgeDBOrWasm(ev, typeHeader, showFileName);
664      this.judgeZip(typeHeader);
665    };
666  }
667
668  private judgeDBOrWasm(ev: File, typeHeader: Blob, showFileName: string): void {
669    let fileReader: FileReader | null = new FileReader();
670    fileReader.readAsText(typeHeader);
671    fileReader.onload = (event): void => {
672      let headerStr: string = `${fileReader?.result}`;
673      SpApplication.traceType = headerStr;
674      if (headerStr.indexOf('SQLite') === 0) {
675        info('Parse trace headerStr sql mode');
676        this.wasm = false;
677        this.handleSqliteMode(ev, showFileName, ev.size, ev.name);
678      } else {
679        info('Parse trace using wasm mode ');
680        this.wasm = true;
681        this.handleWasmMode(ev, showFileName, ev.size, ev.name);
682      }
683    };
684  }
685
686  private judgeZip(typeHeader: Blob): void {
687    const fileReader = new FileReader();
688    fileReader.readAsArrayBuffer(typeHeader);
689    fileReader.onload = (event):void => {
690      const uint8Array = new Uint8Array(event.target!.result as ArrayBuffer);
691      this.isZipFile = isZipFile(uint8Array) || isZlibFile(uint8Array);
692      if (this.isZipFile) {
693        this.cutTraceFile!.style.display = this.isZipFile ? 'none' : 'block';
694      }
695    };
696  }
697
698  private openDistributedTraceFile(ev: InputEvent): void {
699    this.openFileInit(true);
700    this.importConfigDiv!.style.display = 'none';
701    this.closeKeyPath!.style.display = 'none';
702    // @ts-ignore
703    let fileList = ev.detail as FileList;
704    if (fileList.length === 2) {
705      this.handleDistributedWasmMode(fileList.item(0)!, fileList.item(1)!);
706    } else {
707      this.progressEL!.loading = false;
708      this.litSearch!.setPercent('load distributed trace error', -1);
709      this.resetMenus();
710      this.freshMenuDisable(false);
711      this.headerDiv!.style.pointerEvents = 'auto';
712    }
713  }
714
715  private openLineFileHandler(urlParams: URLSearchParams, jsonStr: string): void {
716    Utils.currentTraceMode = TraceMode.NORMAL;
717    this.openFileInit();
718    this.openMenu(false);
719    let downloadLineFile = urlParams.get('local') ? false : true;
720    this.setProgress(downloadLineFile ? 'download trace file' : 'open trace file');
721    this.downloadOnLineFile(
722      urlParams.get('trace') as string,
723      downloadLineFile,
724      (arrayBuf, fileName, showFileName, fileSize) => {
725        if (fileName.split('.').reverse()[0] === 'db') {
726          this.wasm = false;
727          this.handleSqliteMode(new File([arrayBuf], fileName), showFileName, fileSize, fileName, jsonStr);
728        } else {
729          this.handleWasmMode(new File([arrayBuf], fileName), showFileName, fileSize, fileName, jsonStr);
730        }
731      },
732      (localPath) => {
733        let path = urlParams.get('trace') as string;
734        let fileName: string = '';
735        let showFileName: string = '';
736        if (urlParams.get('local')) {
737          this.openMenu(true);
738          fileName = urlParams.get('traceName') as string;
739        } else {
740          fileName = path.split('/').reverse()[0];
741        }
742        this.traceFileName = fileName;
743        showFileName = fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.'));
744        TraceRow.rangeSelectObject = undefined;
745        let localUrl = downloadLineFile ? `${window.location.origin}${localPath}` : urlParams.get('trace')!;
746        fetch(localUrl)
747          .then((res) => {
748            res.arrayBuffer().then((arrayBuf) => {
749              if (urlParams.get('local')) {
750                URL.revokeObjectURL(localUrl);
751              }
752              this.handleWasmMode(new File([arrayBuf], fileName), showFileName, arrayBuf.byteLength, fileName, jsonStr);
753            });
754          })
755          .catch((e) => {
756            if (!downloadLineFile) {
757              const firstQuestionMarkIndex = window.location.href.indexOf('?');
758              location.replace(window.location.href.substring(0, firstQuestionMarkIndex));
759            }
760          });
761      }
762    );
763  }
764
765  private openMenu(open: boolean): void {
766    if (this.mainMenu) {
767      this.mainMenu.style.width = open ? '248px' : '0px';
768      this.mainMenu.style.zIndex = open ? '2000' : '0';
769    }
770    if (this.sidebarButton) {
771      this.sidebarButton.style.width = open ? '0px' : '48px';
772      this.importConfigDiv!.style.left = open ? '5px' : '45px';
773      this.closeKeyPath!.style.left = open ? '25px' : '65px';
774    }
775  }
776
777  private initGlobalDropEvents(): void {
778    let body = document.querySelector('body');
779    body!.addEventListener(
780      'drop',
781      (e) => {
782        e.preventDefault();
783        e.stopPropagation();
784        if (this.rootEL!.classList.contains('filedrag')) {
785          this.rootEL!.classList.remove('filedrag');
786        }
787        //@ts-ignore
788        if (e.dataTransfer.items !== undefined && e.dataTransfer.items.length > 0) {
789          //@ts-ignore
790          let item = e.dataTransfer.items[0];
791          if (item.webkitGetAsEntry()?.isFile) {
792            this.openTraceFile(item.getAsFile()!);
793          } else if (item.webkitGetAsEntry()?.isDirectory) {
794            this.litSearch!.setPercent('This File is not supported!', -1);
795            this.progressEL!.loading = false;
796            this.freshMenuDisable(false);
797            this.mainMenu!.menus!.splice(2, 1);
798            this.mainMenu!.menus = this.mainMenu!.menus!;
799            this.spSystemTrace!.reset(null);
800          }
801        }
802      },
803      false
804    );
805  }
806  private initGlobalEvents(): void {
807    let body = document.querySelector('body');
808    body!.addEventListener(
809      'dragover',
810      (e) => {
811        e.preventDefault();
812        e.stopPropagation();
813        //@ts-ignore
814        if (e.dataTransfer.items.length > 0 && e.dataTransfer.items[0].kind === 'file') {
815          //@ts-ignore
816          e.dataTransfer.dropEffect = 'copy';
817          if (!this.rootEL!.classList.contains('filedrag')) {
818            this.rootEL!.classList.add('filedrag');
819          }
820        }
821      },
822      false
823    );
824    body!.addEventListener(
825      'dragleave',
826      (e) => {
827        e.stopPropagation();
828        e.preventDefault();
829        if (this.rootEL!.classList.contains('filedrag')) {
830          this.rootEL!.classList.remove('filedrag');
831        }
832      },
833      false
834    );
835    this.initGlobalDropEvents();
836  }
837
838  private initDocumentListener(): void {
839    document.addEventListener('file-error', () => {
840      this.litSearch!.setPercent('This File is Error!', -1);
841    });
842    document.addEventListener('file-correct', () => {
843      this.litSearch!.setPercent('', 101);
844    });
845    document.addEventListener('visibilitychange', () => {
846      if (document.visibilityState === 'visible') {
847        this.validateFileCacheLost();
848        if (window.localStorage.getItem('Theme') === 'dark') {
849          this.changeTheme(Theme.DARK);
850        } else {
851          this.changeTheme(Theme.LIGHT);
852        }
853      }
854    });
855    document.addEventListener('keydown', (event) => {
856      const e = event || window.event;
857      const ctrlKey = e.ctrlKey || e.metaKey;
858      //@ts-ignore
859      if (ctrlKey && this.keyCodeMap[e.keyCode]) {
860        e.preventDefault();
861      } else if (e.detail) {
862        // Firefox
863        event.returnValue = false;
864      }
865    });
866    document.body.addEventListener(
867      'wheel',
868      (e) => {
869        if (e.ctrlKey) {
870          if (e.deltaY < 0) {
871            e.preventDefault();
872            return false;
873          }
874          if (e.deltaY > 0) {
875            e.preventDefault();
876            return false;
877          }
878        }
879      },
880      { passive: false }
881    );
882  }
883
884  private initNavigationMenu(multiTrace: boolean): MenuGroup[] {
885    return [
886      {
887        collapsed: false,
888        title: 'Navigation',
889        second: false,
890        icon: 'caret-down',
891        describe: 'Open or record a new trace',
892        children: [
893          {
894            title: 'Open trace file',
895            icon: 'folder',
896            fileChoose: true,
897            fileHandler: (ev: CustomEvent): void => {
898              Utils.currentTraceMode = TraceMode.NORMAL;
899              this.openTraceFile(ev.detail);
900            },
901          },
902          {
903            title: 'Record new trace',
904            icon: 'copyhovered',
905            clickHandler: (item: MenuItem): void => this.clickHandleByRecordNewTrace(),
906          },
907          {
908            title: 'Record template',
909            icon: 'copyhovered',
910            clickHandler: (item: MenuItem): void => this.clickHandleByRecordTemplate(),
911          },
912        ],
913      },
914      {
915        collapsed: !multiTrace,
916        title: 'Open multiple trace',
917        second: false,
918        icon: 'caret-down',
919        describe: 'long trace or distributed trace',
920        children: [
921          {
922            title: 'Open long trace',
923            icon: 'folder',
924            fileChoose: true,
925            clickHandler: (ev: CustomEvent): void => {
926              Utils.currentTraceMode = TraceMode.LONG_TRACE;
927              this.openLongTraceFile(ev, true);
928            },
929            fileHandler: (ev: CustomEvent): void => {
930              Utils.currentTraceMode = TraceMode.LONG_TRACE;
931              this.openLongTraceFile(ev);
932            },
933          },
934          {
935            title: 'Open distributed trace',
936            icon: 'folder',
937            multi: true,
938            fileChoose: true,
939            fileHandler: (ev: InputEvent): void => {
940              Utils.currentTraceMode = TraceMode.DISTRIBUTED;
941              this.openDistributedTraceFile(ev);
942            },
943          },
944        ],
945      },
946    ];
947  }
948
949  private initSupportMenus(): MenuGroup {
950    return {
951      collapsed: false,
952      title: 'Support',
953      second: false,
954      icon: 'caret-down',
955      describe: 'Support',
956      children: [
957        {
958          title: 'Help Documents',
959          icon: 'smart-help',
960          clickHandler: (item: MenuItem): void => this.clickHandleByHelpDocuments(),
961        },
962        {
963          title: 'Flags',
964          icon: 'menu',
965          clickHandler: (item: MenuItem): void => this.clickHandleByFlags(),
966        },
967        {
968          title: 'Keyboard Shortcuts',
969          icon: 'smart-help',
970          clickHandler: (item: MenuItem): void => this.clickHandleByKeyboardShortcuts(),
971        }
972      ],
973    };
974  }
975
976  private clickHandleByHelpDocuments(): void {
977    this.spHelp!.dark = this.dark;
978    this.search = false;
979    this.showContent(this.spHelp!);
980    SpStatisticsHttpUtil.addOrdinaryVisitAction({
981      event: 'help_page',
982      action: 'help_doc',
983    });
984    this.changeUrl();
985  }
986
987  private clickHandleByFlags(): void {
988    this.returnOriginalUrl();
989    this.search = false;
990    this.showContent(this.spFlags!);
991    SpStatisticsHttpUtil.addOrdinaryVisitAction({
992      event: 'flags',
993      action: 'flags',
994    });
995  }
996
997  private clickHandleByKeyboardShortcuts(): void {
998    this.returnOriginalUrl();
999    document
1000      .querySelector('body > sp-application')!
1001      .shadowRoot!.querySelector<HTMLDivElement>('#sp-keyboard')!.style.visibility = 'visible';
1002    SpSystemTrace.keyboardFlar = false;
1003    SpStatisticsHttpUtil.addOrdinaryVisitAction({
1004      event: 'Keyboard Shortcuts',
1005      action: 'Keyboard Shortcuts',
1006    });
1007  }
1008
1009  private clickHandleByRecordNewTrace(): void {
1010    this.returnOriginalUrl();
1011    this.spRecordTrace!.synchronizeDeviceList();
1012    this.spRecordTrace!.record_template = 'false';
1013    this.spRecordTrace!.reConfigPage();
1014    this.spRecordTrace!.refreshConfig(true);
1015    this.showContent(this.spRecordTrace!);
1016  }
1017
1018  private clickHandleByRecordTemplate(): void {
1019    this.returnOriginalUrl();
1020    this.spRecordTrace!.refreshHint();
1021    this.spRecordTrace!.record_template = 'true';
1022    this.spRecordTrace!.reConfigPage();
1023    this.spRecordTrace!.refreshConfig(false);
1024    this.spRecordTrace!.synchronizeDeviceList();
1025    this.showContent(this.spRecordTrace!);
1026  }
1027
1028  private changeUrl(): void {
1029    let url = new URL(window.location.href);
1030    let actionParam = url.searchParams.get('action');
1031    let newActionValue = 'help';
1032    if (actionParam) {
1033      url.searchParams.set('action', newActionValue);
1034    } else {
1035      url.searchParams.append('action', newActionValue);
1036    }
1037    let newURL = url.href;
1038    history.pushState({}, '', newURL);
1039  }
1040
1041  private returnOriginalUrl(): void {
1042    history.pushState({}, '', window.location.origin + window.location.pathname);
1043  }
1044
1045  private handleSqliteMode(ev: unknown, showFileName: string, fileSize: number, fileName: string, jsonStr?: string): void {
1046    let fileSizeStr = (fileSize / 1048576).toFixed(1);
1047    postLog(fileName, fileSizeStr);
1048    document.title = `${showFileName} (${fileSizeStr}M)`;
1049    this.litSearch!.setPercent('', 0);
1050    threadPool.init('sqlite').then((res) => {
1051      let reader = new FileReader();
1052      reader.readAsArrayBuffer(ev as Blob);
1053      reader.onloadend = (ev): void => {
1054        SpApplication.loadingProgress = 0;
1055        SpApplication.progressStep = 3;
1056        let data = this.markPositionHandler(reader.result as ArrayBuffer);
1057        this.spSystemTrace!.loadDatabaseArrayBuffer(
1058          data,
1059          '',
1060          (command: string, _: number) => {
1061            this.setProgress(command);
1062          },
1063          false,
1064          () => {
1065            if (this.markJson) {
1066              window.publish(window.SmartEvent.UI.ImportRecord, this.markJson);
1067            }
1068            if (jsonStr) {
1069              window.publish(window.SmartEvent.UI.ImportRecord, jsonStr);
1070            }
1071            this.mainMenu!.menus!.splice(2, this.mainMenu!.menus!.length > 2 ? 1 : 0, {
1072              collapsed: false,
1073              title: 'Current Trace',
1074              second: false,
1075              icon: 'caret-down',
1076              describe: 'Actions on the current trace',
1077              children: this.getTraceOptionMenus(showFileName, fileSizeStr, fileName, true, false),
1078            });
1079            this.mainMenu!.menus!.splice(3, 1, {
1080              collapsed: false,
1081              title: 'Support',
1082              second: false,
1083              icon: 'caret-down',
1084              describe: 'Support',
1085              children: this.getTraceSupportMenus(),
1086            });
1087            this.litSearch!.setPercent('', 101);
1088            this.chartFilter!.setAttribute('mode', '');
1089            this.progressEL!.loading = false;
1090            this.freshMenuDisable(false);
1091            this.spInfoAndStats!.initInfoAndStatsData();
1092            this.cutTraceFile!.style.display = 'none';
1093            this.exportRecord!.style.display = 'none';
1094            this.headerDiv!.style.pointerEvents = 'auto';
1095          }
1096        );
1097      };
1098    });
1099  }
1100
1101  private handleDistributedWasmMode(file1: File, file2: File): void {
1102    this.litSearch!.setPercent('', 1);
1103    document.title = 'Distributed Trace';
1104    let completeHandler = async (res: unknown): Promise<void> => {
1105      await this.traceLoadCompleteHandler(res, '', '', file1.name, true, file2.name);
1106    };
1107    let typeHeader = file1.slice(0, 6);
1108    let reader: FileReader | null = new FileReader();
1109    reader.readAsText(typeHeader);
1110    reader.onloadend = (event): void => {
1111      let headerStr: string = `${reader?.result}`;
1112      let traceType = 'wasm';
1113      if (headerStr.indexOf('SQLite') === 0) {
1114        traceType = 'sqlite';
1115      }
1116      Promise.all([threadPool.init(traceType), threadPool2.init(traceType)]).then(() => {
1117        let wasmUrl = `https://${window.location.host.split(':')[0]}:${window.location.port}/application/wasm.json`;
1118        Promise.all([file1.arrayBuffer(), file2.arrayBuffer()]).then((bufArr) => {
1119          this.litSearch!.setPercent('ArrayBuffer loaded  ', 2);
1120          SpApplication.loadingProgress = 0;
1121          SpApplication.progressStep = 2;
1122          let buf1 = this.markPositionHandler(bufArr[0]);
1123          let buf2 = this.markPositionHandler(bufArr[1]);
1124          info('initData start Parse Data');
1125          this.spSystemTrace!.loadDatabaseArrayBuffer(
1126            buf1,
1127            wasmUrl,
1128            (command: string, _: number) => this.setProgress(command),
1129            true,
1130            completeHandler,
1131            buf2,
1132            file1.name,
1133            file2.name
1134          );
1135        });
1136      });
1137    };
1138  }
1139
1140  private handleWasmMode(ev: unknown, showFileName: string, fileSize: number, fileName: string, jsonStr?: string): void {
1141    this.litSearch!.setPercent('', 1);
1142    if (fileName.endsWith('.json')) {
1143      this.progressEL!.loading = true;
1144      //@ts-ignore
1145      self.spSystemTrace!.loadSample(ev).then(() => {
1146        this.showContent(this.spSystemTrace!);
1147        this.litSearch!.setPercent('', 101);
1148        this.freshMenuDisable(false);
1149        this.chartFilter!.setAttribute('mode', '');
1150        this.progressEL!.loading = false;
1151      });
1152    } else if (fileName.endsWith('.csv')) {
1153      this.progressEL!.loading = true;
1154      this.spSystemTrace!.loadGpuCounter(ev as File).then(() => {
1155        this.showContent(this.spSystemTrace!);
1156        this.litSearch!.setPercent('', 101);
1157        this.freshMenuDisable(false);
1158        this.chartFilter!.setAttribute('mode', '');
1159        this.progressEL!.loading = false;
1160      });
1161    } else {
1162      let fileSizeStr = (fileSize / 1048576).toFixed(1);
1163      postLog(fileName, fileSizeStr);
1164      document.title = `${showFileName} (${fileSizeStr}M)`;
1165      info('Parse trace using wasm mode ');
1166      let completeHandler = async (res: unknown): Promise<void> => {
1167        await this.traceLoadCompleteHandler(res, fileSizeStr, showFileName, fileName, false);
1168        if (this.markJson) {
1169          window.publish(window.SmartEvent.UI.ImportRecord, this.markJson);
1170        }
1171        if (jsonStr) {
1172          window.publish(window.SmartEvent.UI.ImportRecord, jsonStr);
1173        }
1174      };
1175      threadPool.init('wasm').then((res) => {
1176        let reader: FileReader = new FileReader();
1177        //@ts-ignore
1178        reader.readAsArrayBuffer(ev);
1179        reader.onloadend = (ev): void => {
1180          info('read file onloadend');
1181          this.litSearch!.setPercent('ArrayBuffer loaded  ', 2);
1182          let wasmUrl = `https://${window.location.host.split(':')[0]}:${window.location.port}/application/wasm.json`;
1183          SpApplication.loadingProgress = 0;
1184          SpApplication.progressStep = 3;
1185          let data = this.markPositionHandler(reader.result as ArrayBuffer);
1186          info('initData start Parse Data');
1187          this.spSystemTrace!.loadDatabaseArrayBuffer(
1188            data,
1189            wasmUrl,
1190            (command: string, _: number) => this.setProgress(command),
1191            false,
1192            completeHandler
1193          );
1194        };
1195      });
1196    }
1197  }
1198
1199  private markPositionHandler(buf: ArrayBuffer): ArrayBuffer {
1200    const decoder = new TextDecoder('utf-8');
1201    const headText = decoder.decode(buf.slice(0, 100));
1202    let hasMark = headText.includes('MarkPositionJSON');
1203    if (hasMark) {
1204      let markLength = headText.split('->')[0].replace('MarkPositionJSON', '');
1205      let mark = decoder.decode(buf.slice(0, markLength.length + parseInt(markLength)));
1206      if (mark.includes('->')) {
1207        this.markJson = mark.split('->')[1];
1208      }
1209      return buf.slice(markLength.length + parseInt(markLength));
1210    } else {
1211      return buf;
1212    }
1213  }
1214
1215  private async traceLoadCompleteHandler(
1216    res: unknown,
1217    fileSize: string,
1218    showFileName: string,
1219    fileName: string,
1220    isDistributed: boolean,
1221    fileName2?: string
1222  ): Promise<void> {
1223    let index = 3;
1224    if (isDistributed) {
1225      //分布式,隐藏 裁剪 trace 和 导出带记录的收藏trace 功能
1226      this.cutTraceFile!.style.display = 'none';
1227      this.exportRecord!.style.display = 'none';
1228      this.litSearch?.setAttribute('distributed', '');
1229      setThreadPoolTraceBuffer('1', null);
1230      setThreadPoolTraceBuffer('2', null);
1231    } else {
1232      let existFtrace = await queryExistFtrace();
1233      let isAllowTrace = true;
1234      let sharedBuffer = getThreadPoolTraceBuffer('1');
1235      if (sharedBuffer) {
1236        let traceHeadData = new Uint8Array(sharedBuffer!.slice(0, 10));
1237        let enc = new TextDecoder();
1238        let headerStr = enc.decode(traceHeadData);
1239        let rowTraceStr = Array.from(new Uint8Array(sharedBuffer!.slice(0, 2)))
1240          .map((byte) => byte.toString(16).padStart(2, '0'))
1241          .join('');
1242        if (headerStr.indexOf('OHOSPROF') !== 0 && rowTraceStr.indexOf('49df') !== 0) {
1243          isAllowTrace = false;
1244        }
1245        this.cutTraceFile!.style.display = this.isZipFile ? 'none' : 'block';
1246        this.exportRecord!.style.display = 'block';
1247        setThreadPoolTraceBuffer('1', null);
1248      }
1249      if (existFtrace.length > 0 && isAllowTrace) {
1250        this.showConvertTraceMenu(fileName);
1251        index = 4;
1252      }
1253    }
1254
1255    this.loadTraceCompleteMenuHandler(index);
1256    //@ts-ignore
1257    if (res.status) {
1258      info('loadDatabaseArrayBuffer success');
1259      if (isDistributed) {
1260        Utils.distributedTrace.push(fileName || 'trace1');
1261        Utils.distributedTrace.push(fileName2 || 'trace2');
1262        this.litSearch!.setTraceSelectOptions();
1263      } else {
1264        //@ts-ignore
1265        (window as unknown).traceFileName = fileName;
1266      }
1267      this.showCurrentTraceMenu(fileSize, showFileName, fileName, isDistributed);
1268      SpApplication.isTraceLoaded = true;
1269      if (!isDistributed) {
1270        this.importConfigDiv!.style.display = Utils.getInstance().getSchedSliceMap().size > 0 ? 'block' : 'none';
1271      }
1272      this.showContent(this.spSystemTrace!);
1273      this.litSearch!.setPercent('', 101);
1274      this.chartFilter!.setAttribute('mode', '');
1275      this.freshMenuDisable(false);
1276    } else {
1277      info('loadDatabaseArrayBuffer failed');
1278      //@ts-ignore
1279      this.litSearch!.setPercent(res.msg || 'This File is not supported!', -1);
1280      this.resetMenus();
1281      this.freshMenuDisable(false);
1282    }
1283    this.progressEL!.loading = false;
1284    this.headerDiv!.style.pointerEvents = 'auto';
1285    this.spInfoAndStats!.initInfoAndStatsData();
1286  }
1287
1288  private resetMenus(multiTrace: boolean = false): void {
1289    this.mainMenu!.menus = [...this.initNavigationMenu(multiTrace), this.initSupportMenus()];
1290  }
1291
1292  private showConvertTraceMenu(fileName: string): void {
1293    this.mainMenu!.menus!.splice(3, 1, {
1294      collapsed: false,
1295      title: 'Convert trace',
1296      second: false,
1297      icon: 'caret-down',
1298      describe: 'Convert to other formats',
1299      children: this.pushConvertTrace(fileName),
1300    });
1301  }
1302
1303  private showCurrentTraceMenu(fileSize: string, showFileName: string, fileName: string, isDistributed: boolean): void {
1304    this.mainMenu!.menus!.splice(2, this.mainMenu!.menus!.length > 2 ? 1 : 0, {
1305      collapsed: false,
1306      title: 'Current Trace',
1307      second: false,
1308      icon: 'caret-down',
1309      describe: 'Actions on the current trace',
1310      children: this.getTraceOptionMenus(showFileName, fileSize, fileName, false, isDistributed),
1311    });
1312  }
1313
1314  private loadTraceCompleteMenuHandler(index: number): void {
1315    this.mainMenu!.menus!.splice(index, 1, {
1316      collapsed: false,
1317      title: 'Support',
1318      second: false,
1319      icon: 'caret-down',
1320      describe: 'Support',
1321      children: [
1322        {
1323          title: 'Help Documents',
1324          icon: 'smart-help',
1325          clickHandler: (item: MenuItem): void => this.clickHandleByHelpDocuments(),
1326        },
1327        {
1328          title: 'Flags',
1329          icon: 'menu',
1330          clickHandler: (item: MenuItem): void => this.clickHandleByFlags(),
1331        },
1332        {
1333          title: 'Keyboard Shortcuts',
1334          icon: 'smart-help',
1335          clickHandler: (item: MenuItem): void => this.clickHandleByKeyboardShortcuts(),
1336        },
1337      ],
1338    });
1339  }
1340
1341  private validateGetTraceFileByPage(): boolean {
1342    if (!this.wasm) {
1343      this.progressEL!.loading = false;
1344      return false;
1345    }
1346    return this.pageTimStamp !== 0;
1347  }
1348
1349  private queryFileByPage(
1350    instance: LongTraceDBUtils,
1351    indexedDbPageNum: number,
1352    maxTraceFileLength: number,
1353    traceRange: IDBKeyRange
1354  ): void {
1355    instance.indexedDBHelp.get(instance.tableName, traceRange, 'QueryFileByPage').then((result) => {
1356      let traceData = indexedDataToBufferData(result);
1357      let ebpfRange = this.getIDBKeyRange(indexedDbPageNum, 'ebpf_new');
1358      let arkTsRange = this.getIDBKeyRange(indexedDbPageNum, 'arkts_new');
1359      let hiperfRange = this.getIDBKeyRange(indexedDbPageNum, 'hiperf_new');
1360      Promise.all([
1361        instance.getByRange(ebpfRange),
1362        instance.getByRange(arkTsRange),
1363        instance.getByRange(hiperfRange),
1364      ]).then((otherResult) => {
1365        let ebpfData = indexedDataToBufferData(otherResult[0]);
1366        let arkTsData = indexedDataToBufferData(otherResult[1]);
1367        let hiperfData = indexedDataToBufferData(otherResult[2]);
1368        let traceArray = new Uint8Array(traceData);
1369        let ebpfArray = new Uint8Array(ebpfData);
1370        let arkTsArray = new Uint8Array(arkTsData);
1371        let hiPerfArray = new Uint8Array(hiperfData);
1372        let allOtherData = [ebpfData, arkTsData, hiperfData];
1373        let otherDataLength = traceData.byteLength + ebpfData.byteLength + arkTsData.byteLength + hiperfData.byteLength;
1374        let timeStamp =
1375          this.currentDataTime[0] +
1376          this.currentDataTime[1] +
1377          this.currentDataTime[2] +
1378          '_' +
1379          this.currentDataTime[3] +
1380          this.currentDataTime[4] +
1381          this.currentDataTime[5];
1382        this.traceFileName = `hiprofiler_long_${timeStamp}_${indexedDbPageNum}.htrace`;
1383        if (otherDataLength > maxTraceFileLength) {
1384          if (traceData.byteLength > maxTraceFileLength) {
1385            this.traceFileLoadFailedHandler('hitrace file too big!');
1386          } else {
1387            let freeDataLength = maxTraceFileLength - traceData.byteLength;
1388            let freeDataIndex = findFreeSizeAlgorithm(
1389              [ebpfData.byteLength, arkTsData.byteLength, hiperfData.byteLength],
1390              freeDataLength
1391            );
1392            let finalData = [traceData];
1393            freeDataIndex.forEach((dataIndex) => {
1394              finalData.push(allOtherData[dataIndex]);
1395            });
1396            const file = new File([new Blob(finalData)], this.traceFileName);
1397            this.handleWasmMode(file, file.name, file.size, this.traceFileName);
1398          }
1399        } else {
1400          let fileBlob = new Blob([traceArray, ebpfArray, arkTsArray, hiPerfArray]);
1401          const file = new File([fileBlob], this.traceFileName);
1402          this.handleWasmMode(file, file.name, file.size, file.name);
1403        }
1404        this.refreshLongTraceButtonStyle();
1405        this.longTracePage!.style.display = 'flex';
1406      });
1407    });
1408  }
1409
1410  private refreshLongTraceButtonStyle(): void {
1411    let pageListDiv = this.shadowRoot?.querySelector('.page-number-list') as HTMLDivElement;
1412    let pageNodeList = pageListDiv.querySelectorAll<HTMLDivElement>('div');
1413    let pageInput = this.shadowRoot?.querySelector<HTMLInputElement>('.page-input');
1414    let previewButton: HTMLDivElement | null | undefined =
1415      this.shadowRoot?.querySelector<HTMLDivElement>('#preview-button');
1416    let nextButton: HTMLDivElement | null | undefined = this.shadowRoot?.querySelector<HTMLDivElement>('#next-button');
1417    let pageConfirmEl = this.shadowRoot?.querySelector<HTMLDivElement>('.confirm-button');
1418    pageInput!.style.pointerEvents = 'auto';
1419    pageNodeList.forEach((pageItem) => {
1420      pageItem.style.pointerEvents = 'auto';
1421    });
1422    nextButton!.style.pointerEvents = 'auto';
1423    nextButton!.style.opacity = '1';
1424    previewButton!.style.pointerEvents = 'auto';
1425    previewButton!.style.opacity = '1';
1426    if (this.currentPageNum === 1) {
1427      previewButton!.style.pointerEvents = 'none';
1428      previewButton!.style.opacity = '0.7';
1429    } else if (this.currentPageNum === this.longTraceHeadMessageList.length) {
1430      nextButton!.style.pointerEvents = 'none';
1431      nextButton!.style.opacity = '0.7';
1432    }
1433    //
1434    pageConfirmEl!.style.pointerEvents = 'auto';
1435  }
1436
1437  private getTraceFileByPage(pageNumber: number): void {
1438    this.openFileInit(true);
1439    if (this.validateGetTraceFileByPage()) {
1440      let indexedDbPageNum = pageNumber - 1;
1441      let maxTraceFileLength = 400 * 1024 * 1024;
1442      let traceRange = this.getIDBKeyRange(indexedDbPageNum, 'trace');
1443      let instance = LongTraceDBUtils.getInstance();
1444      this.queryFileByPage(instance, indexedDbPageNum, maxTraceFileLength, traceRange);
1445    }
1446  }
1447
1448  private traceFileLoadFailedHandler(reason: string): void {
1449    this.litSearch!.isLoading = false;
1450    this.litSearch!.setPercent(reason, -1);
1451    this.progressEL!.loading = false;
1452    this.freshMenuDisable(false);
1453  }
1454
1455  private getIDBKeyRange(indexedDbPageNum: number, key: string): IDBKeyRange {
1456    return IDBKeyRange.bound(
1457      [this.pageTimStamp, key, indexedDbPageNum],
1458      [this.pageTimStamp, key, indexedDbPageNum],
1459      false,
1460      false
1461    );
1462  }
1463
1464  private refreshPageListHandler(
1465    pageListDiv: HTMLDivElement,
1466    previewButton: HTMLDivElement,
1467    nextButton: HTMLDivElement,
1468    pageInput: HTMLInputElement
1469  ): void {
1470    this.progressEL!.loading = true;
1471    this.refreshPageList(
1472      pageListDiv,
1473      previewButton!,
1474      nextButton!,
1475      pageInput!,
1476      this.currentPageNum,
1477      this.longTraceHeadMessageList.length
1478    );
1479    this.getTraceFileByPage(this.currentPageNum);
1480  }
1481
1482  private sendCutFileMessage(timStamp: number): void {
1483    this.pageTimStamp = timStamp;
1484    threadPool.init('wasm').then(() => {
1485      let headUintArray = new Uint8Array(this.longTraceHeadMessageList.length * 1024);
1486      let headOffset = 0;
1487      this.longTraceHeadMessageList = this.longTraceHeadMessageList.sort(
1488        (leftMessage, rightMessage) => leftMessage.pageNum - rightMessage.pageNum
1489      );
1490      for (let index = 0; index < this.longTraceHeadMessageList.length; index++) {
1491        let currentUintArray = new Uint8Array(this.longTraceHeadMessageList[index].data);
1492        headUintArray.set(currentUintArray, headOffset);
1493        headOffset += currentUintArray.length;
1494      }
1495      threadPool.submit(
1496        'ts-cut-file',
1497        '',
1498        {
1499          headArray: headUintArray,
1500          timeStamp: timStamp,
1501          splitFileInfo: this.longTraceTypeMessageMap?.get(0),
1502          splitDataList: this.longTraceDataList,
1503        },
1504        (res: Array<unknown>) => {
1505          this.litSearch!.setPercent('Cut in file ', 100);
1506          this.currentDataTime = getCurrentDataTime();
1507          if (this.longTraceHeadMessageList.length > 0) {
1508            this.getTraceFileByPage(this.currentPageNum);
1509            this.litSearch!.style.marginLeft = '80px';
1510            this.longTracePage!.style.display = 'flex';
1511            this.initCutFileEvent();
1512          } else {
1513            this.progressEL!.loading = false;
1514            this.litSearch!.setPercent('Missing basic trace in the large-file scenario!', -1);
1515            this.freshMenuDisable(false);
1516            return;
1517          }
1518        },
1519        'long_trace'
1520      );
1521    });
1522  }
1523
1524  private initCutFileEvent(): void {
1525    let pageListDiv = this.shadowRoot?.querySelector('.page-number-list') as HTMLDivElement;
1526    let previewButton: HTMLDivElement | null | undefined =
1527      this.shadowRoot?.querySelector<HTMLDivElement>('#preview-button');
1528    let nextButton: HTMLDivElement | null | undefined = this.shadowRoot?.querySelector<HTMLDivElement>('#next-button');
1529    let pageInput = this.shadowRoot?.querySelector<HTMLInputElement>('.page-input');
1530    pageListDiv.innerHTML = '';
1531    this.refreshPageList(
1532      pageListDiv,
1533      previewButton!,
1534      nextButton!,
1535      pageInput!,
1536      this.currentPageNum,
1537      this.longTraceHeadMessageList.length
1538    );
1539    this.initCutFileNextOrPreEvents(previewButton!, nextButton!, pageListDiv, pageInput!);
1540    let nodeListOf = pageListDiv.querySelectorAll<HTMLDivElement>('div');
1541    nodeListOf.forEach((divEL, index) => {
1542      divEL.addEventListener('click', () => {
1543        if (this.progressEL!.loading) {
1544          return;
1545        }
1546        if (divEL.textContent === '...') {
1547          let freeSize = Number(nodeListOf[index + 1].textContent) - Number(nodeListOf[index - 1].textContent);
1548          this.currentPageNum = Math.floor(freeSize / 2 + Number(nodeListOf[index - 1].textContent));
1549        } else {
1550          this.currentPageNum = Number(divEL.textContent);
1551        }
1552        this.refreshPageListHandler(pageListDiv, previewButton!, nextButton!, pageInput!);
1553      });
1554    });
1555    pageInput!.addEventListener('input', () => {
1556      let value = pageInput!.value;
1557      value = value.replace(/\D/g, '');
1558      if (value) {
1559        value = Math.min(this.longTraceHeadMessageList.length, parseInt(value)).toString();
1560      }
1561      pageInput!.value = value;
1562    });
1563    let pageConfirmEl = this.shadowRoot?.querySelector<HTMLDivElement>('.confirm-button');
1564    pageConfirmEl!.addEventListener('click', () => {
1565      if (this.progressEL!.loading) {
1566        return;
1567      }
1568      this.currentPageNum = Number(pageInput!.value);
1569      this.refreshPageListHandler(pageListDiv, previewButton!, nextButton!, pageInput!);
1570    });
1571  }
1572
1573  private initCutFileNextOrPreEvents(
1574    previewButton: HTMLDivElement,
1575    nextButton: HTMLDivElement,
1576    pageListDiv: HTMLDivElement,
1577    pageInput: HTMLInputElement
1578  ): void {
1579    if (previewButton) {
1580      previewButton.addEventListener('click', () => {
1581        if (this.progressEL!.loading || this.currentPageNum === 1) {
1582          return;
1583        }
1584        if (this.currentPageNum > 1) {
1585          this.currentPageNum--;
1586          this.refreshPageListHandler(pageListDiv, previewButton!, nextButton!, pageInput!);
1587        }
1588      });
1589    }
1590    nextButton!.addEventListener('click', () => {
1591      if (this.progressEL!.loading || this.currentPageNum === this.longTraceHeadMessageList.length) {
1592        return;
1593      }
1594      if (this.currentPageNum < this.longTraceHeadMessageList.length) {
1595        this.currentPageNum++;
1596        this.refreshPageListHandler(pageListDiv, previewButton!, nextButton!, pageInput!);
1597      }
1598    });
1599  }
1600
1601  private initCustomColorHandler(): void {
1602    let customColorShow = this.shadowRoot
1603      ?.querySelector('lit-main-menu')!
1604      .shadowRoot!.querySelector('.customColor') as HTMLDivElement;
1605    customColorShow.addEventListener('click', (ev) => {
1606      if (this!.hasAttribute('custom-color')) {
1607        this!.removeAttribute('custom-color');
1608        this.customColor!.setAttribute('hidden', '');
1609        this.customColor!.cancelOperate();
1610      } else {
1611        this!.removeAttribute('chart_filter');
1612        this.chartFilter!.setAttribute('hidden', '');
1613        this!.setAttribute('custom-color', '');
1614        this.customColor!.removeAttribute('hidden');
1615      }
1616    });
1617  }
1618
1619  private openFileInit(multiTrace: boolean = false): void {
1620    clearTraceFileCache();
1621    this.litSearch!.clear();
1622    this.spAiAnalysisPage!.clear();
1623    Utils.currentSelectTrace = undefined;
1624    this.markJson = undefined;
1625    if (!multiTrace) {
1626      this.longTracePage!.style.display = 'none';
1627    }
1628    this.litSearch?.removeAttribute('distributed');
1629    SpStatisticsHttpUtil.addOrdinaryVisitAction({
1630      event: 'open_trace',
1631      action: 'open_trace',
1632    });
1633    info('openTraceFile');
1634    this.headerDiv!.style.pointerEvents = 'none';
1635    this.spSystemTrace!.clearPointPair();
1636    this.spSystemTrace!.reset((command: string, percent: number): void => {
1637      this.setProgress(command);
1638    });
1639    threadPool2.reset().then();
1640    window.publish(window.SmartEvent.UI.MouseEventEnable, {
1641      mouseEnable: false,
1642    });
1643    window.clearTraceRowComplete();
1644    SpSchedulingAnalysis.resetCpu();
1645    this.resetMenus(multiTrace);
1646    this.freshMenuDisable(true);
1647    this.showContent(this.spSystemTrace!);
1648    this.progressEL!.loading = true;
1649  }
1650
1651  private restoreDownLoadIcons(): void {
1652    let querySelectorAll = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group');
1653    querySelectorAll!.forEach((menuGroup) => {
1654      let attribute = menuGroup.getAttribute('title');
1655      if (attribute === 'Convert trace') {
1656        let querySelectors = menuGroup.querySelectorAll<LitMainMenuItem>('lit-main-menu-item');
1657        querySelectors.forEach((item) => {
1658          if (item.getAttribute('title') === 'Convert to .systrace') {
1659            item!.setAttribute('icon', 'download');
1660            let querySelector = item!.shadowRoot?.querySelector('.icon') as LitIcon;
1661            querySelector.removeAttribute('spin');
1662          }
1663        });
1664      }
1665    });
1666  }
1667
1668  private postConvert(fileName: string): void {
1669    let newFileName = fileName.substring(0, fileName.lastIndexOf('.')) + '.systrace';
1670    let aElement = document.createElement('a');
1671    convertPool.submitWithName('getConvertData', (status: boolean, msg: string, results: Blob) => {
1672      aElement.href = URL.createObjectURL(results);
1673      aElement.download = newFileName;
1674      let timeoutId = 0;
1675      aElement.addEventListener('click', (ev) => {
1676        clearTimeout(timeoutId);
1677        timeoutId = window.setTimeout(() => {
1678          this.restoreDownLoadIcons();
1679        }, 2000);
1680      });
1681      aElement.click();
1682      window.URL.revokeObjectURL(aElement.href);
1683    });
1684  }
1685
1686  private pushConvertTrace(fileName: string): Array<MenuItem> {
1687    let instance = this;
1688    let menus = [];
1689    menus.push({
1690      title: 'Convert to .systrace',
1691      icon: 'download',
1692      clickHandler: function () {
1693        convertPool.init('convert').then((item) => {
1694          let querySelectorAll =
1695            instance.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group');
1696          querySelectorAll!.forEach((menuGroup) => {
1697            let attribute = menuGroup.getAttribute('title');
1698            if (attribute === 'Convert trace') {
1699              let querySelectors = menuGroup.querySelectorAll<LitMainMenuItem>('lit-main-menu-item');
1700              querySelectors.forEach((item) => {
1701                if (item.getAttribute('title') === 'Convert to .systrace') {
1702                  item!.setAttribute('icon', 'convert-loading');
1703                  item!.classList.add('pending');
1704                  item!.style.fontKerning = '';
1705                  let querySelector = item!.shadowRoot?.querySelector('.icon') as LitIcon;
1706                  querySelector.setAttribute('spin', '');
1707                }
1708              });
1709            }
1710          });
1711          instance.postConvert(fileName);
1712        });
1713      },
1714    });
1715    return menus;
1716  }
1717
1718  private setProgress(command: string): void {
1719    if (command === 'database ready' && SpApplication.loadingProgress < 50) {
1720      SpApplication.progressStep = 6;
1721    }
1722    if (command === 'process' && SpApplication.loadingProgress < 92) {
1723      SpApplication.loadingProgress = 92 + Math.round(Math.random() * SpApplication.progressStep);
1724    } else {
1725      SpApplication.loadingProgress += Math.round(Math.random() * SpApplication.progressStep + Math.random());
1726    }
1727    if (SpApplication.loadingProgress > 99) {
1728      SpApplication.loadingProgress = 99;
1729    }
1730    info('setPercent :' + command + 'percent :' + SpApplication.loadingProgress);
1731    this.litSearch!.setPercent(command + '  ', SpApplication.loadingProgress);
1732  }
1733
1734  private getTraceOptionMenus(
1735    showFileName: string,
1736    fileSize: string,
1737    fileName: string,
1738    isServer: boolean,
1739    isDistributed: boolean,
1740    dbName?: string
1741  ): Array<MenuItem> {
1742    let menus = [
1743      {
1744        title: isDistributed ? 'Distributed Trace' : `${showFileName} (${fileSize}M)`,
1745        icon: 'file-fill',
1746        clickHandler: (): void => {
1747          this.search = true;
1748          this.showContent(this.spSystemTrace!);
1749        },
1750      },
1751    ];
1752    if (
1753      !isDistributed &&
1754      Utils.getInstance().getWinCpuCount() > 0 &&
1755      FlagsConfig.getFlagsConfigEnableStatus('SchedulingAnalysis')
1756    ) {
1757      menus.push({
1758        title: 'Scheduling Analysis',
1759        icon: 'piechart-circle-fil',
1760        clickHandler: (): void => {
1761          SpStatisticsHttpUtil.addOrdinaryVisitAction({
1762            event: 'Scheduling Analysis',
1763            action: 'scheduling_analysis',
1764          });
1765          this.showContent(this.spSchedulingAnalysis!);
1766          this.spSchedulingAnalysis!.init();
1767        },
1768      });
1769    }
1770    if (!isDistributed) {
1771      menus.push({
1772        title: 'Download File',
1773        icon: 'download', // @ts-ignore
1774        fileModel: this.wasm ? 'wasm' : 'db',
1775        clickHandler: (): void => {
1776          this.download(this.mainMenu!, fileName, isServer, dbName);
1777          SpStatisticsHttpUtil.addOrdinaryVisitAction({
1778            event: 'download',
1779            action: 'download',
1780          });
1781        },
1782      });
1783      menus.push({
1784        title: 'Download Database',
1785        icon: 'download', // @ts-ignore
1786        fileModel: this.wasm ? 'wasm' : 'db',
1787        clickHandler: (): void => {
1788          this.downloadDB(this.mainMenu!, fileName);
1789          SpStatisticsHttpUtil.addOrdinaryVisitAction({
1790            event: 'download_db',
1791            action: 'download',
1792          });
1793        },
1794      });
1795      this.getTraceQuerySqlMenus(menus);
1796    }
1797    return menus;
1798  }
1799
1800  private getTraceSupportMenus(): Array<MenuItem> {
1801    return [
1802      {
1803        title: 'Help Documents',
1804        icon: 'smart-help',
1805        clickHandler: (item: MenuItem): void => {
1806          this.spHelp!.dark = this.dark;
1807          this.search = false;
1808          this.showContent(this.spHelp!);
1809          SpStatisticsHttpUtil.addOrdinaryVisitAction({
1810            event: 'help_page',
1811            action: 'help_doc',
1812          });
1813        },
1814      },
1815      {
1816        title: 'Flags',
1817        icon: 'menu',
1818        fileModel: this.wasm ? 'wasm' : 'db',
1819        clickHandler: (item: MenuItem): void => {
1820          this.search = false;
1821          this.showContent(this.spFlags!);
1822          SpStatisticsHttpUtil.addOrdinaryVisitAction({
1823            event: 'flags',
1824            action: 'flags',
1825          });
1826        },
1827      },
1828      {
1829        title: 'Keyboard Shortcuts',
1830        icon: 'smart-help',
1831        clickHandler: (item: MenuItem): void => {
1832          document
1833            .querySelector('body > sp-application')!
1834            .shadowRoot!.querySelector<HTMLDivElement>('#sp-keyboard')!.style.visibility = 'visible';
1835          SpStatisticsHttpUtil.addOrdinaryVisitAction({
1836            event: 'Keyboard Shortcuts',
1837            action: 'Keyboard Shortcuts',
1838          });
1839        },
1840      },
1841      {
1842        title: '第三方文件',
1843        icon: 'file-fill',
1844        fileModel: this.wasm ? 'wasm' : 'db',
1845        clickHandler: (item: MenuItem): void => {
1846          this.search = false;
1847          this.showContent(this.spThirdParty!);
1848        },
1849      },
1850    ];
1851  }
1852
1853  private getTraceQuerySqlMenus(menus: Array<unknown>): void {
1854    if (this.querySql) {
1855      if (this.spQuerySQL) {
1856        this.spQuerySQL!.reset();
1857        menus.push({
1858          title: 'Query (SQL)',
1859          icon: 'filesearch',
1860          clickHandler: () => {
1861            this.showContent(this.spQuerySQL!);
1862          },
1863        });
1864      }
1865      if (this.spMetrics) {
1866        this.spMetrics!.reset();
1867        menus.push({
1868          title: 'Metrics',
1869          icon: 'metric',
1870          fileModel: this.wasm ? 'wasm' : 'db',
1871          clickHandler: () => {
1872            this.showContent(this.spMetrics!);
1873          },
1874        });
1875      }
1876      if (this.spInfoAndStats) {
1877        menus.push({
1878          title: 'Info and stats',
1879          icon: 'info',
1880          clickHandler: () => {
1881            SpStatisticsHttpUtil.addOrdinaryVisitAction({
1882              event: 'info',
1883              action: 'info_stats',
1884            });
1885            this.showContent(this.spInfoAndStats!);
1886          },
1887        });
1888      }
1889    }
1890  }
1891
1892  private initSlideMenuEvents(): void {
1893    //打开侧边栏
1894    this.sidebarButton!.onclick = (e): void => {
1895      if (this.sidebarButton) {
1896        this.sidebarButton.style.width = '0px';
1897        this.importConfigDiv!.style.left = '5px';
1898        this.contentLeftOption!.style.left = '5px';
1899        this.closeKeyPath!.style.left = '25px';
1900      }
1901      if (this.mainMenu) {
1902        this.mainMenu.style.width = '248px';
1903        this.mainMenu.style.zIndex = '2000';
1904        this.mainMenu.style.display = 'flex';
1905      }
1906    };
1907    let icon: HTMLDivElement | undefined | null = this.mainMenu?.shadowRoot?.querySelector('div.header > div');
1908    icon!.style.pointerEvents = 'none';
1909    icon!.onclick = (e): void => {
1910      if (this.mainMenu) {
1911        this.mainMenu.style.width = '0px';
1912        this.mainMenu.style.display = 'flex';
1913        this.mainMenu.style.zIndex = '0';
1914      }
1915      if (this.sidebarButton) {
1916        this.sidebarButton.style.width = '48px';
1917        this.importConfigDiv!.style.left = '45px';
1918        this.contentLeftOption!.style.left = '45px';
1919        this.closeKeyPath!.style.left = '65px';
1920      }
1921    };
1922  }
1923
1924  private initImportConfigEvent(): void {
1925    this.importFileBt?.addEventListener('change', (): void => {
1926      let files = this.importFileBt!.files;
1927      if (files && files.length === 1) {
1928        const reader = new FileReader();
1929        reader.readAsText(files[0], 'UTF-8');
1930        reader.onload = (e): void => {
1931          if (e.target?.result) {
1932            try {
1933              const result = parseKeyPathJson(e.target.result as string);
1934              window.publish(window.SmartEvent.UI.KeyPath, result);
1935              this.closeKeyPath!.style.display = 'block';
1936            } catch {
1937              error('json Parse Failed');
1938              this.litSearch!.setPercent('Json Parse Failed!', -1);
1939              window.setTimeout(() => {
1940                this.litSearch!.setPercent('Json Parse Failed!', 101);
1941              }, 1000);
1942            }
1943          } else {
1944            window.publish(window.SmartEvent.UI.KeyPath, []);
1945            this.closeKeyPath!.style.display = 'none';
1946          }
1947        };
1948      }
1949      this.importFileBt!.files = null;
1950      this.importFileBt!.value = '';
1951    });
1952    if (this.closeKeyPath) {
1953      this.closeKeyPath.addEventListener('click', (): void => {
1954        window.publish(window.SmartEvent.UI.KeyPath, []);
1955        this.closeKeyPath!.style.display = 'none';
1956      });
1957    }
1958  }
1959
1960  private initCustomEvents(): void {
1961    window.subscribe(window.SmartEvent.UI.MenuTrace, () => this.showContent(this.spSystemTrace!));
1962    window.subscribe(window.SmartEvent.UI.Error, (err) => {
1963      //@ts-ignore
1964      this.litSearch!.setPercent(err, -1);
1965      this.progressEL!.loading = false;
1966      this.freshMenuDisable(false);
1967    }); //@ts-ignore
1968    window.subscribe(window.SmartEvent.UI.Loading, (arg: { loading: boolean; text?: string }) => {
1969      if (arg.text) {
1970        this.litSearch!.setPercent(arg.text || '', arg.loading ? -1 : 101);
1971      }
1972      if (this.headerDiv) {
1973        this.headerDiv.style.pointerEvents = arg.loading ? 'none' : 'auto';
1974      }
1975      window.publish(window.SmartEvent.UI.MouseEventEnable, {
1976        mouseEnable: !arg.loading,
1977      });
1978      this.progressEL!.loading = arg.loading;
1979    });
1980  }
1981
1982  private initEvents(): void {
1983    this.addEventListener('copy', function (event) {
1984      SpSystemTrace.isMouseLeftDown = false;
1985      let clipdata = event.clipboardData;
1986      let value = clipdata!.getData('text/plain');
1987      let searchValue = value.toString().trim();
1988      clipdata!.setData('text/plain', searchValue);
1989    });
1990    this.initSearchEvents();
1991    this.initSystemTraceEvents();
1992    this.filterConfig!.addEventListener('click', (ev) => {
1993      SpSystemTrace.isMouseLeftDown = false;
1994      this.filterRowConfigClickHandle();
1995    });
1996    this.configClose!.addEventListener('click', (ev) => {
1997      this.filterRowConfigClickHandle();
1998    });
1999    this.cutTraceFile!.addEventListener('click', (ev) => {
2000      this.croppingFile(this.progressEL!, this.litSearch!);
2001    });
2002
2003    let aiAnalysis = this.shadowRoot
2004      ?.querySelector('lit-main-menu')!
2005      .shadowRoot!.querySelector('.ai_analysis') as HTMLDivElement;
2006    aiAnalysis!.addEventListener('click', (ev) => {
2007      if (this.spAiAnalysisPage!.style.visibility === 'hidden' || this.spAiAnalysisPage!.style.display === 'none') {
2008        this.spAiAnalysisPage!.style.display = 'block';
2009        this.spAiAnalysisPage!.style.visibility = 'visible';
2010      } else {
2011        this.spAiAnalysisPage!.style.visibility = 'hidden';
2012        this.spAiAnalysisPage!.style.display = 'none';
2013      }
2014    })
2015
2016    this.spAiAnalysisPage!.valueChangeHandler = (value: string, id: number): void => {
2017      this.litSearch!.valueChangeHandler!(this.litSearch!.trimSideSpace(value), id);
2018    };
2019
2020    // 鼠标拖动改变大小
2021    this.aiPageResize();
2022  }
2023
2024  private aiPageResize(): void {
2025    const resizableDiv = this.spAiAnalysisPage!;
2026    let isResizing = false;
2027
2028    resizableDiv.addEventListener('mousemove', (e) => {
2029      if (Math.abs(e.clientX - resizableDiv.getBoundingClientRect().left) < 5) {
2030        resizableDiv.style.cursor = 'e-resize';
2031      } else {
2032        resizableDiv.style.cursor = 'default';
2033      }
2034    })
2035
2036    resizableDiv.addEventListener('mousedown', function (e) {
2037      isResizing = true;
2038      if (e.clientX - resizableDiv.getBoundingClientRect().left < 5) {
2039        document.addEventListener('mousemove', changeAiWidth);
2040      }
2041      document.addEventListener('mouseup', mouseUp);
2042    });
2043
2044
2045    function changeAiWidth(e: unknown): void {
2046      resizableDiv.style.cursor = 'e-resize';
2047      // @ts-ignore
2048      resizableDiv.style.width = window.innerWidth - e.clientX + 'px';
2049    }
2050
2051    function mouseUp(): void {
2052      isResizing = false;
2053      document.removeEventListener('mousemove', changeAiWidth);
2054      document.removeEventListener('mouseup', mouseUp);
2055    }
2056  }
2057
2058  private filterRowConfigClickHandle(): void {
2059    if (this!.hasAttribute('chart_filter')) {
2060      this!.removeAttribute('chart_filter');
2061      this.chartFilter!.setAttribute('hidden', '');
2062    } else {
2063      this!.removeAttribute('custom-color');
2064      this.customColor!.setAttribute('hidden', '');
2065      this.customColor!.cancelOperate();
2066      this!.setAttribute('chart_filter', '');
2067      this.chartFilter!.removeAttribute('hidden');
2068    }
2069  }
2070
2071  private initRecordEvents(): void {
2072    this.exportRecord?.addEventListener('click', () => {
2073      this.headerDiv!.style.pointerEvents = 'none';
2074      window.publish(window.SmartEvent.UI.Loading, { loading: true, text: 'Downloading trace file with mark' });
2075      window.publish(window.SmartEvent.UI.ExportRecord, { bt: this.exportRecord });
2076    });
2077  }
2078
2079  private initSearchChangeEvents(): void {
2080    let timer: NodeJS.Timeout;
2081    this.litSearch!.valueChangeHandler = (value: string, id: number = -1): void => {
2082      Utils.currentSelectTrace = this.litSearch?.getSearchTraceId();
2083      this.litSearch!.currenSearchValue = value;
2084      if (value.length > 0) {
2085        this.progressEL!.loading = true;
2086      } else {
2087        this.progressEL!.loading = false;
2088      }
2089      if (this.litSearch!.list.length > 0) {
2090        let currentEntry = this.litSearch!.list[this.litSearch!.index];
2091        cancelCurrentTraceRowHighlight(this.spSystemTrace!, currentEntry);
2092      }
2093      this.litSearch!.list = [];
2094      if (timer) {
2095        clearTimeout(timer);
2096      }
2097      timer = setTimeout(() => {
2098        this.litSearch!.isClearValue = false;
2099        if (value.length > 0) {
2100          let list = [];
2101          this.spSystemTrace!.searchCPU(value).then((cpus) => {
2102            list = cpus;
2103            let asyncFuncArr = this.spSystemTrace!.seachAsyncFunc(value);
2104            this.spSystemTrace!.searchFunction(list, asyncFuncArr, value).then((mixedResults) => {
2105              if (this.litSearch!.searchValue !== '' || id > -1) {
2106                if (!Utils.isDistributedMode()) {
2107                  this.litSearch!.list = this.spSystemTrace!.searchSdk(mixedResults, value);
2108                } else {
2109                  this.litSearch!.list = mixedResults;
2110                }
2111                if (id > -1) {
2112                  this.litSearch!.list = this.litSearch!.list.filter((v: unknown) => {
2113                    // @ts-ignore
2114                    return v.id === id;
2115                  });
2116                  let input = this.litSearch!.shadowRoot?.querySelector('input') as HTMLInputElement;
2117                  // @ts-ignore
2118                  input.value = this.litSearch!.list[0]!.funName;
2119                }
2120                this.litSearch!.index = this.spSystemTrace!.showStruct(false, -1, this.litSearch!.list);
2121              }
2122              this.progressEL!.loading = false;
2123            });
2124          });
2125        } else {
2126          let indexEL = this.litSearch!.shadowRoot!.querySelector<HTMLSpanElement>('#index');
2127          indexEL!.textContent = '0';
2128          this.litSearch!.list = [];
2129          this.spSystemTrace?.visibleRows.forEach((it) => {
2130            it.highlight = false;
2131            it.draw();
2132          });
2133          this.spSystemTrace?.timerShaftEL?.removeTriangle('inverted');
2134        }
2135      }, 1000);
2136    };
2137  }
2138  private initSearchEvents(): void {
2139    this.litSearch!.addEventListener('previous-data', (ev) => {
2140      if (this.progressEL!.loading) {
2141        return;
2142      }
2143      this.litSearch!.index = this.spSystemTrace!.showStruct(true, this.litSearch!.index, this.litSearch!.list);
2144      this.litSearch!.blur();
2145    });
2146    this.litSearch!.addEventListener('next-data', (ev) => {
2147      if (this.progressEL!.loading) {
2148        return;
2149      }
2150      this.litSearch!.index = this.spSystemTrace!.showStruct(false, this.litSearch!.index, this.litSearch!.list);
2151      this.litSearch!.blur();
2152    });
2153    // 翻页事件
2154    this.litSearch!.addEventListener('retarget-data', (ev) => {
2155      if (this.progressEL!.loading) {
2156        return;
2157      }
2158      this.litSearch!.index = this.spSystemTrace!.showStruct(
2159        false,
2160        //@ts-ignore
2161        ev.detail.value,
2162        this.litSearch!.list,
2163        //@ts-ignore
2164        ev.detail.value
2165      );
2166      this.litSearch!.blur();
2167    });
2168    this.litSearch!.addEventListener('trace-change', (e): void => {
2169      this.spSystemTrace?.clickEmptyArea();
2170      this.spSystemTrace!.searchTargetTraceHandler();
2171    });
2172    this.initSearchChangeEvents();
2173  }
2174
2175  private initSystemTraceEvents(): void {
2176    this.spSystemTrace?.addEventListener('trace-previous-data', (ev) => {
2177      if (this.progressEL!.loading) {
2178        return;
2179      }
2180      this.litSearch!.index = this.spSystemTrace!.showStruct(true, this.litSearch!.index, this.litSearch!.list);
2181    });
2182    this.spSystemTrace?.addEventListener('trace-next-data', (ev) => {
2183      if (this.progressEL!.loading) {
2184        return;
2185      }
2186      this.litSearch!.index = this.spSystemTrace!.showStruct(false, this.litSearch!.index, this.litSearch!.list);
2187    });
2188  }
2189
2190  private showContent(showNode: HTMLElement): void {
2191    if (showNode === this.spSystemTrace) {
2192      this.menu!.style.pointerEvents = 'auto';
2193      this.sidebarButton!.style.pointerEvents = 'auto';
2194      this.search = true;
2195      this.litRecordSearch!.style.display = 'none';
2196      this.litSearch!.style.display = 'block';
2197      this.contentRightOption!.style.display = 'flex';
2198      this.contentLeftOption!.style.display = 'flex';
2199      this.contentCenterOption!.style.display = 'flex';
2200      window.publish(window.SmartEvent.UI.KeyboardEnable, {
2201        enable: true,
2202      });
2203      this.filterConfig!.style.visibility = 'visible';
2204    } else {
2205      this.removeAttribute('custom-color');
2206      this.customColor!.setAttribute('hidden', '');
2207      this.customColor!.cancelOperate();
2208      this.menu!.style.pointerEvents = 'none';
2209      this.sidebarButton!.style.pointerEvents = 'none';
2210      this.search = this.litSearch!.isLoading;
2211      this.contentRightOption!.style.display = 'none';
2212      this.contentLeftOption!.style.display = 'none';
2213      this.contentCenterOption!.style.display = 'none';
2214      if (!this.search) {
2215        this.litSearch!.style.display = 'none';
2216        this.litRecordSearch!.style.display = 'block';
2217      }
2218      window.publish(window.SmartEvent.UI.KeyboardEnable, {
2219        enable: false,
2220      });
2221      this.filterConfig!.style.visibility = 'hidden';
2222      this.removeAttribute('chart_filter');
2223      this.chartFilter!.setAttribute('hidden', '');
2224    }
2225    this.childComponent!.forEach((node) => {
2226      if (this.hasAttribute('chart_filter')) {
2227        this.removeAttribute('chart_filter');
2228      }
2229      if (this.hasAttribute('custom-color')) {
2230        this.removeAttribute('custom-color');
2231        this.customColor!.setAttribute('hidden', '');
2232        this.customColor!.cancelOperate();
2233      }
2234      if (node === showNode) {
2235        showNode.style.visibility = 'visible';
2236        let recordSetting = document.querySelector('body > sp-application')?.shadowRoot?.querySelector('#sp-record-trace')?.shadowRoot?.querySelector('#app-content > record-setting');
2237        ShadowRootInput.preventBubbling(recordSetting!);
2238        //@ts-ignore
2239      } else if (node.id! === 'sp-ai-analysis' && node.style!.visibility! === 'visible') {
2240        return;
2241      } else {
2242        (node! as HTMLElement).style.visibility = 'hidden';
2243      }
2244    });
2245  }
2246
2247  private validateFileCacheLost(): void {
2248    caches.has(getThreadPoolTraceBufferCacheKey('1')).then((exist): void => {
2249      if (!exist) {
2250        this.mainMenu!.menus?.forEach((mg) => {
2251          mg.children.forEach((mi: MenuItem) => {
2252            if (mi.title === 'Download File') {
2253              mi.disabled = true;
2254            }
2255          });
2256        });
2257        this.cutTraceFile!.style.display = 'none';
2258        this.mainMenu!.menus = this.mainMenu!.menus;
2259      }
2260    });
2261  }
2262
2263  private refreshPageList(
2264    pageListDiv: HTMLDivElement,
2265    previewButton: HTMLDivElement,
2266    nextButton: HTMLDivElement,
2267    pageInput: HTMLInputElement,
2268    currentPageNum: number,
2269    maxPageNumber: number
2270  ): void {
2271    if (pageInput) {
2272      pageInput.textContent = currentPageNum.toString();
2273      pageInput.value = currentPageNum.toString();
2274      pageInput.style.pointerEvents = 'none';
2275    }
2276    let pageText: string[] = [];
2277    if (maxPageNumber > 7) {
2278      pageText = this.largePageHandler(currentPageNum, maxPageNumber, previewButton, nextButton);
2279    } else {
2280      pageText = [];
2281      for (let index = 0; index < maxPageNumber; index++) {
2282        pageText.push((index + 1).toString());
2283      }
2284    }
2285    this.pageNodeHandler(pageListDiv, pageText, currentPageNum);
2286  }
2287
2288  private pageNodeHandler(pageListDiv: HTMLDivElement, pageText: Array<string>, currentPageNum: number): void {
2289    let pageNodeList = pageListDiv.querySelectorAll<HTMLDivElement>('div');
2290    if (pageNodeList.length > 0) {
2291      pageNodeList.forEach((page, index) => {
2292        page.textContent = pageText[index].toString();
2293        page.title = pageText[index];
2294        if (currentPageNum.toString() === pageText[index]) {
2295          page.setAttribute('selected', '');
2296        } else {
2297          if (page.hasAttribute('selected')) {
2298            page.removeAttribute('selected');
2299          }
2300        }
2301      });
2302    } else {
2303      pageListDiv.innerHTML = '';
2304      pageText.forEach((page) => {
2305        let element = document.createElement('div');
2306        element.className = 'page-number pagination';
2307        element.textContent = page.toString();
2308        element.title = page.toString();
2309        if (currentPageNum.toString() === page.toString()) {
2310          element.setAttribute('selected', '');
2311        }
2312        pageListDiv.appendChild(element);
2313      });
2314    }
2315  }
2316
2317  private largePageHandler(
2318    currentPageNum: number,
2319    maxPageNumber: number,
2320    previewButton: HTMLDivElement,
2321    nextButton: HTMLDivElement
2322  ): Array<string> {
2323    switch (currentPageNum) {
2324      case 1:
2325      case 2:
2326      case 3:
2327      case 4:
2328      case 5:
2329        return ['1', '2', '3', '4', '5', '...', maxPageNumber.toString()];
2330      case maxPageNumber:
2331      case maxPageNumber - 1:
2332      case maxPageNumber - 2:
2333      case maxPageNumber - 3:
2334      case maxPageNumber - 4:
2335        return [
2336          '1',
2337          '...',
2338          (maxPageNumber - 4).toString(),
2339          (maxPageNumber - 3).toString(),
2340          (maxPageNumber - 2).toString(),
2341          (maxPageNumber - 1).toString(),
2342          maxPageNumber.toString(),
2343        ];
2344      default:
2345        nextButton.style.pointerEvents = 'auto';
2346        previewButton!.style.pointerEvents = 'auto';
2347        nextButton.style.opacity = '1';
2348        previewButton!.style.opacity = '1';
2349        return [
2350          '1',
2351          '...',
2352          (currentPageNum - 1).toString(),
2353          currentPageNum.toString(),
2354          (currentPageNum + 1).toString(),
2355          '...',
2356          maxPageNumber.toString(),
2357        ];
2358    }
2359  }
2360
2361  /**
2362   * 修改颜色或者主题,重新绘制侧边栏和泳道图
2363   * @param theme 当前主题(深色和浅色)
2364   * @param colorsArray 预览的情况下传入
2365   */
2366  changeTheme(theme: Theme, colorsArray?: Array<string>): void {
2367    if (!colorsArray) {
2368      this.customColor!.setRadioChecked(theme);
2369    }
2370    if (theme === Theme.DARK) {
2371      this.changeDarkTheme(colorsArray);
2372    } else {
2373      this.changeLightTheme(colorsArray);
2374    }
2375    this.spSystemTrace!.timerShaftEL!.rangeRuler!.draw();
2376    if (this.colorTransiton) {
2377      clearTimeout(this.colorTransiton);
2378    }
2379    this.colorTransiton = setTimeout(() => (this.mainMenu!.style.transition = '0s'), 1000);
2380  }
2381
2382  private changeDarkTheme(colorsArray?: Array<string>): void {
2383    let menuGroup = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group');
2384    let menuItem = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuItem>('lit-main-menu-item');
2385    this.mainMenu!.style.backgroundColor = '#262f3c';
2386    this.mainMenu!.style.transition = '1s';
2387    menuGroup!.forEach((item) => {
2388      let groupName = item!.shadowRoot!.querySelector('.group-name') as LitMainMenuGroup;
2389      let groupDescribe = item!.shadowRoot!.querySelector('.group-describe') as LitMainMenuGroup;
2390      groupName.style.color = 'white';
2391      groupDescribe.style.color = 'white';
2392    });
2393    menuItem!.forEach((item) => {
2394      item.style.color = 'white';
2395    });
2396    if (
2397      !colorsArray &&
2398      window.localStorage.getItem('DarkThemeColors') &&
2399      ColorUtils.FUNC_COLOR_B !== JSON.parse(window.localStorage.getItem('DarkThemeColors')!)
2400    ) {
2401      ColorUtils.MD_PALETTE = JSON.parse(window.localStorage.getItem('DarkThemeColors')!);
2402      ColorUtils.FUNC_COLOR = JSON.parse(window.localStorage.getItem('DarkThemeColors')!);
2403    } else if (colorsArray) {
2404      ColorUtils.MD_PALETTE = colorsArray;
2405      ColorUtils.FUNC_COLOR = colorsArray;
2406    } else {
2407      ColorUtils.MD_PALETTE = ColorUtils.FUNC_COLOR_B;
2408      ColorUtils.FUNC_COLOR = ColorUtils.FUNC_COLOR_B;
2409    }
2410  }
2411
2412  private changeLightTheme(colorsArray?: Array<string>): void {
2413    let menuGroup = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group');
2414    let menuItem = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuItem>('lit-main-menu-item');
2415    this.mainMenu!.style.backgroundColor = 'white';
2416    this.mainMenu!.style.transition = '1s';
2417    menuGroup!.forEach((item) => {
2418      let groupName = item!.shadowRoot!.querySelector('.group-name') as LitMainMenuGroup;
2419      let groupDescribe = item!.shadowRoot!.querySelector('.group-describe') as LitMainMenuGroup;
2420      groupName.style.color = 'black';
2421      groupDescribe.style.color = '#92959b';
2422    });
2423    menuItem!.forEach((item) => {
2424      item.style.color = 'black';
2425    });
2426    if (
2427      !colorsArray &&
2428      window.localStorage.getItem('LightThemeColors') &&
2429      ColorUtils.FUNC_COLOR_A !== JSON.parse(window.localStorage.getItem('LightThemeColors')!)
2430    ) {
2431      ColorUtils.MD_PALETTE = JSON.parse(window.localStorage.getItem('LightThemeColors')!);
2432      ColorUtils.FUNC_COLOR = JSON.parse(window.localStorage.getItem('LightThemeColors')!);
2433    } else if (colorsArray) {
2434      ColorUtils.MD_PALETTE = colorsArray;
2435      ColorUtils.FUNC_COLOR = colorsArray;
2436    } else {
2437      ColorUtils.MD_PALETTE = ColorUtils.FUNC_COLOR_A;
2438      ColorUtils.FUNC_COLOR = ColorUtils.FUNC_COLOR_A;
2439    }
2440  }
2441
2442  private downloadOnLineFile(
2443    url: string,
2444    download: boolean,
2445    openUrl: (buffer: ArrayBuffer, fileName: string, showFileName: string, fileSize: number) => void,
2446    openFileHandler: (path: string) => void
2447  ): void {
2448    if (download) {
2449      fetch(url)
2450        .then((res) => {
2451          res.arrayBuffer().then((arrayBuf) => {
2452            let urlParams = new URL(url).searchParams;
2453            let fileName = urlParams.get('name') ? decodeURIComponent(urlParams.get('name')!) : url.split('/').reverse()[0];
2454            this.traceFileName = fileName;
2455            let showFileName =
2456              fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.'));
2457            openUrl(arrayBuf, fileName, showFileName, arrayBuf.byteLength);
2458          });
2459        })
2460        .catch((e) => {
2461          let api = `${window.location.origin}/application/download-file`;
2462          fetch(api, {
2463            method: 'POST',
2464            headers: {
2465              'Content-Type': 'application/x-www-form-urlencoded',
2466            },
2467            body: new URLSearchParams({
2468              url: url,
2469            }),
2470          })
2471            .then((response) => response.json())
2472            .then((res) => {
2473              if (res.code === 0 && res.success) {
2474                let resultUrl = res.data.url;
2475                if (resultUrl) {
2476                  openFileHandler(resultUrl.toString().replace(/\\/g, '/'));
2477                }
2478              }
2479            });
2480        });
2481    } else {
2482      openFileHandler(url);
2483    }
2484  }
2485
2486  private croppingFile(progressEL: LitProgressBar, litSearch: LitSearch): void {
2487    let cutLeftNs = TraceRow.rangeSelectObject?.startNS || 0;
2488    let cutRightNs = TraceRow.rangeSelectObject?.endNS || 0;
2489    if (cutRightNs === cutLeftNs) {
2490      return;
2491    }
2492    let recordStartNS = window.recordStartNS;
2493    let cutLeftTs = recordStartNS + cutLeftNs;
2494    let cutRightTs = recordStartNS + cutRightNs;
2495    let minCutDur = 1_000_000;
2496    if (cutRightTs - cutLeftTs < minCutDur) {
2497      let unitTs = (cutRightTs - cutLeftTs) / 2;
2498      let midTs = cutLeftTs + unitTs;
2499      cutLeftTs = midTs - minCutDur / 2;
2500      cutRightTs = midTs + minCutDur / 2;
2501    }
2502    progressEL.loading = true;
2503    threadPool.cutFile(cutLeftTs, cutRightTs, (status: boolean, msg: string, cutBuffer?: ArrayBuffer) => {
2504      progressEL.loading = false;
2505      if (status) {
2506        FlagsConfig.updateFlagsConfig('FfrtConvert', 'Disabled');
2507        let traceFileName = this.traceFileName as string;
2508        let cutIndex = traceFileName.indexOf('_cut_');
2509        let fileType = traceFileName.substring(traceFileName.lastIndexOf('.'));
2510        let traceName = document.title.replace(/\s*\([^)]*\)/g, '').trim();
2511        if (cutIndex !== -1) {
2512          traceName = traceName.substring(0, cutIndex);
2513        }
2514        if (cutBuffer !== undefined && cutBuffer.byteLength <= 12) {
2515          this.litSearch!.setPercent('The cut is empty data. Select a time range for valid data!', -1);
2516          this.progressEL!.loading = false;
2517          this.freshMenuDisable(false);
2518          return;
2519        }
2520        let blobUrl = URL.createObjectURL(new Blob([cutBuffer!]));
2521        window.open(
2522          `index.html?link=true&local=true&traceName=${encodeURIComponent(
2523            traceName
2524          )}_cut_${cutLeftTs}${fileType}&trace=${encodeURIComponent(blobUrl)}`
2525        );
2526      } else {
2527        litSearch.setPercent(msg, -1);
2528        window.setTimeout(() => {
2529          litSearch.setPercent(msg, 101);
2530        }, 1000);
2531      }
2532    });
2533  }
2534
2535  private downloadDB(mainMenu: LitMainMenu, fileDbName: string): void {
2536    let fileName = `${fileDbName?.substring(0, fileDbName?.lastIndexOf('.'))}.db`;
2537    threadPool.submit(
2538      'download-db',
2539      '',
2540      {},
2541      async (reqBufferDB: ArrayBuffer) => {
2542        let a = document.createElement('a');
2543        a.href = URL.createObjectURL(new Blob([reqBufferDB]));
2544        a.download = fileName;
2545        a.click();
2546        this.itemIconLoading(mainMenu, 'Current Trace', 'Download Database', true);
2547        let timer = setInterval(async () => {
2548          this.itemIconLoading(mainMenu, 'Current Trace', 'Download Database', false);
2549          clearInterval(timer);
2550        }, 4000);
2551        // 存入缓存
2552        const blob = new Blob([reqBufferDB]);
2553        const response = new Response(blob);
2554        caches.open('DB-file').then(cache => {
2555          return cache.put(`/${fileName}`, response);
2556        });
2557      },
2558      'download-db'
2559    );
2560  }
2561
2562  private async download(mainMenu: LitMainMenu, fileName: string, isServer: boolean, dbName?: string): Promise<void> {
2563    let a = document.createElement('a');
2564    if (isServer) {
2565      if (dbName !== '') {
2566        let file = dbName?.substring(0, dbName?.lastIndexOf('.')) + fileName.substring(fileName.lastIndexOf('.'));
2567        a.href = `https://${window.location.host.split(':')[0]}:${window.location.port}${file}`;
2568      } else {
2569        return;
2570      }
2571    } else {
2572      let buffer = await readTraceFileBuffer();
2573      if (buffer) {
2574        a.href = URL.createObjectURL(new Blob([buffer]));
2575      }
2576    }
2577    a.download = fileName;
2578    a.click();
2579    window.URL.revokeObjectURL(a.href);
2580    this.itemIconLoading(mainMenu, 'Current Trace', 'Download File', true);
2581    let timer = setInterval(() => {
2582      this.itemIconLoading(mainMenu, 'Current Trace', 'Download File', false);
2583      clearInterval(timer);
2584    }, 4000);
2585  }
2586
2587  private itemIconLoading(mainMenu: LitMainMenu, groupName: string, itemName: string, start: boolean): void {
2588    let currentTraceGroup = mainMenu.shadowRoot?.querySelector<LitMainMenuGroup>(
2589      `lit-main-menu-group[title='${groupName}']`
2590    );
2591    let downloadItem = currentTraceGroup!.querySelector<LitMainMenuItem>(`lit-main-menu-item[title='${itemName}']`);
2592    let downloadIcon = downloadItem!.shadowRoot?.querySelector('.icon') as LitIcon;
2593    if (start) {
2594      downloadItem!.setAttribute('icon', 'convert-loading');
2595      downloadIcon.setAttribute('spin', '');
2596    } else {
2597      downloadItem!.setAttribute('icon', 'download');
2598      downloadIcon.removeAttribute('spin');
2599    }
2600  }
2601
2602  freshMenuDisable(disable: boolean): void {
2603    // @ts-ignore
2604    this.mainMenu!.menus[0].children[0].disabled = disable;
2605    // @ts-ignore
2606    this.mainMenu!.menus[0].children[1].disabled = disable;
2607    if (this.mainMenu!.menus!.length > 2) {
2608      // @ts-ignore
2609      this.mainMenu!.menus[1].children.map((it) => (it.disabled = disable));
2610    }
2611    this.mainMenu!.menus = this.mainMenu!.menus;
2612    this.filterConfig!.style.visibility = disable ? 'hidden' : 'visible';
2613  }
2614}
2615