• 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 {
17  LogicHandler,
18  ChartStruct,
19  convertJSON,
20  DataCache,
21  HiPerfSymbol,
22  PerfCall,
23} from './ProcedureLogicWorkerCommon';
24import { PerfBottomUpStruct } from '../../bean/PerfBottomUpStruct';
25import { SelectionParam } from '../../bean/BoxSelection';
26import { dealAsyncData } from './ProcedureLogicWorkerCommon';
27
28const systemRuleName: string = '/system/';
29const numRuleName: string = '/max/min/';
30const maxDepth: number = 256;
31
32type PerfThreadMap = {
33  [pid: string]: PerfThread;
34};
35type PerfCallChainMap = {
36  [id: number]: PerfCallChain[];
37};
38type FileMap = {
39  [id: number]: PerfFile[];
40};
41
42type MergeMap = {
43  [id: string]: PerfCallChainMerageData;
44};
45
46type spiltMap = {
47  [id: string]: PerfCallChainMerageData[];
48};
49
50export class ProcedureLogicWorkerPerf extends LogicHandler {
51  filesData: FileMap = {};
52  samplesData: Array<PerfCountSample> = [];
53  threadData: PerfThreadMap = {};
54  callChainData: PerfCallChainMap = {};
55  splitMapData: spiltMap = {};
56  currentTreeMapData: MergeMap = {};
57  currentTreeList: PerfCallChainMerageData[] = [];
58  searchValue: string = '';
59  dataSource: PerfCallChainMerageData[] = [];
60  allProcess: PerfCallChainMerageData[] = [];
61  currentEventId: string = '';
62  isAnalysis: boolean = false;
63  isPerfBottomUp: boolean = false;
64  isHideThread: boolean = false;
65  isHideThreadState: boolean = false;
66  isOnlyKernel: boolean = false;
67  private lib: object | undefined;
68  private symbol: object | undefined;
69  private dataCache = DataCache.getInstance();
70  private isTopDown: boolean = true;
71  // 应对当depth为0是的结构变化后的数据还原
72  private forkAllProcess: PerfCallChainMerageData[] = [];
73  private lineMap: Map<string, Set<number>> = new Map<string, Set<number>>();
74
75  handle(data: unknown): void {
76    //@ts-ignore
77    this.currentEventId = data.id;
78    //@ts-ignore
79    if (data && data.type) {
80      //@ts-ignore
81      switch (data.type) {
82        case 'perf-init':
83          //@ts-ignore
84          this.dataCache.perfCountToMs = data.params.fValue;
85          this.initPerfFiles();
86          break;
87        case 'perf-queryPerfFiles':
88          //@ts-ignore
89          this.perfQueryPerfFiles(data.params.list);
90          break;
91        case 'perf-queryPerfThread':
92          //@ts-ignore
93          this.perfQueryPerfThread(data.params.list);
94          break;
95        case 'perf-queryPerfCalls':
96          //@ts-ignore
97          this.perfQueryPerfCalls(data.params.list);
98          break;
99        case 'perf-queryPerfCallchains':
100          this.perfQueryPerfCallchains(data);
101          break;
102        case 'perf-analysis':
103          this.getAnalysisData(data);
104          break;
105        case 'perf-bottomUp':
106          this.getBottomUpData(data);
107          break;
108        case 'perf-profile':
109          this.getProfileData(data);
110          break;
111        case 'perf-action':
112          this.perfAction(data);
113          break;
114        case 'perf-vaddr-back':
115          this.rebackVaddrList(data);
116          break;
117        case 'perf-vaddr':
118          this.perfGetVaddr(data);
119          break;
120        case 'perf-reset':
121          this.perfReset();
122          break;
123        case 'perf-async':
124          this.perfAsync(data);
125          break;
126      }
127    }
128  }
129
130  private getAnalysisData(data: unknown): void {
131    //@ts-ignore
132    this.samplesData = convertJSON(data.params.list) || [];
133    let result;
134    result = this.resolvingAction([
135      {
136        funcName: 'combineAnalysisCallChain',
137        funcArgs: [true],
138      },
139    ]),
140      self.postMessage({
141        //@ts-ignore
142        id: data.id,
143        //@ts-ignore
144        action: data.action,
145        // @ts-ignore
146        results: result,
147      });
148  }
149
150  private getBottomUpData(data: unknown): void {
151    //@ts-ignore
152    this.samplesData = convertJSON(data.params.list) || [];
153    let result;
154    result = this.resolvingAction([
155      {
156        funcName: 'getBottomUp',
157        funcArgs: [true],
158      },
159    ]);
160    self.postMessage({
161      //@ts-ignore
162      id: data.id,
163      //@ts-ignore
164      action: data.action,
165      // @ts-ignore
166      results: result,
167    });
168  }
169
170  private getProfileData(data: unknown): void {
171    //@ts-ignore
172    this.samplesData = convertJSON(data.params.list) || [];
173    let result;
174    if (this.lib) {
175      let libData = this.combineCallChainForAnalysis(this.lib);
176      this.freshPerfCallchains(libData, this.isTopDown);
177      result = this.allProcess;
178      this.lib = undefined;
179    } else if (this.symbol) {
180      let funData = this.combineCallChainForAnalysis(this.symbol);
181      this.freshPerfCallchains(funData, this.isTopDown);
182      result = this.allProcess;
183      this.symbol = undefined;
184    } else {
185      result = this.resolvingAction([
186        {
187          funcName: 'getCallChainsBySampleIds',
188          funcArgs: [this.isTopDown],
189        },
190      ]);
191    }
192    self.postMessage({
193      //@ts-ignore
194      id: data.id,
195      //@ts-ignore
196      action: data.action,
197      // @ts-ignore
198      results: result,
199    });
200  }
201
202  private perfQueryPerfFiles(list: Array<PerfFile>): void {
203    let files = convertJSON(list) || [];
204    //@ts-ignore
205    files.forEach((file: PerfFile) => {
206      this.filesData[file.fileId] = this.filesData[file.fileId] || [];
207      PerfFile.setFileName(file);
208      this.filesData[file.fileId].push(file);
209    });
210    this.initPerfThreads();
211  }
212  private perfQueryPerfThread(list: Array<PerfThread>): void {
213    let threads = convertJSON(list) || [];
214    //@ts-ignore
215    threads.forEach((thread: PerfThread): void => {
216      this.threadData[thread.tid] = thread;
217    });
218    this.initPerfCalls();
219  }
220  private perfQueryPerfCalls(list: Array<PerfCall>): void {
221    let perfCalls = convertJSON(list) || [];
222    if (perfCalls.length !== 0) {
223      //@ts-ignore
224      perfCalls.forEach((perfCall: PerfCall): void => {
225        this.dataCache.perfCallChainMap.set(perfCall.sampleId, perfCall);
226      });
227    }
228    this.initPerfCallchains();
229  }
230  private perfQueryPerfCallchains(data: unknown): void {
231    //@ts-ignore
232    let arr = convertJSON(data.params.list) || [];
233    this.initPerfCallChainTopDown(arr as PerfCallChain[]);
234
235    self.postMessage({
236      // @ts-ignore
237      id: data.id,
238      // @ts-ignore
239      action: data.action,
240      results: this.dataCache.perfCallChainMap,
241    });
242  }
243  private perfQueryCallchainsGroupSample(data: unknown): void {
244    //@ts-ignore
245    this.samplesData = convertJSON(data.params.list) || [];
246    let result;
247    if (this.isAnalysis) {
248      result = this.resolvingAction([
249        {
250          funcName: 'combineAnalysisCallChain',
251          funcArgs: [true],
252        },
253      ]);
254    } else if (this.isPerfBottomUp) {
255      result = this.resolvingAction([
256        {
257          funcName: 'getBottomUp',
258          funcArgs: [true],
259        },
260      ]);
261    } else {
262      if (this.lib) {
263        let libData = this.combineCallChainForAnalysis(this.lib);
264        this.freshPerfCallchains(libData, this.isTopDown);
265        result = this.allProcess;
266        this.lib = undefined;
267      } else if (this.symbol) {
268        let funData = this.combineCallChainForAnalysis(this.symbol);
269        this.freshPerfCallchains(funData, this.isTopDown);
270        result = this.allProcess;
271        this.symbol = undefined;
272      } else {
273        result = this.resolvingAction([
274          {
275            funcName: 'getCallChainsBySampleIds',
276            funcArgs: [this.isTopDown],
277          },
278        ]);
279      }
280    }
281    self.postMessage({
282      //@ts-ignore
283      id: data.id,
284      //@ts-ignore
285      action: data.action,
286      results: result,
287    });
288    if (this.isAnalysis) {
289      this.isAnalysis = false;
290    }
291  }
292
293  rebackVaddrList(data: unknown): void {
294    // @ts-ignore
295    let vaddrCallchainList = convertJSON(data.params.list);
296    let sampleCallChainList: unknown = [];
297    for (let i = 0; i < vaddrCallchainList.length; i++) {
298      let funcVaddrLastItem = {};
299      // @ts-ignore
300      let callChains = [...this.callChainData[vaddrCallchainList[i].callchain_id]];
301      const lastCallChain = callChains[callChains.length - 1];
302      // @ts-ignore
303      funcVaddrLastItem.callchain_id = lastCallChain.sampleId;
304      // @ts-ignore
305      funcVaddrLastItem.symbolName = this.dataCache.dataDict.get(lastCallChain.name as number);
306      // @ts-ignore
307      funcVaddrLastItem.vaddrInFile = lastCallChain.vaddrInFile;
308      // @ts-ignore
309      funcVaddrLastItem.offsetToVaddr = lastCallChain.offsetToVaddr;
310      // @ts-ignore
311      funcVaddrLastItem.process_id = vaddrCallchainList[i].process_id;
312      // @ts-ignore
313      funcVaddrLastItem.thread_id = vaddrCallchainList[i].thread_id;
314      // @ts-ignore
315      funcVaddrLastItem.count = vaddrCallchainList[i].count;
316      // @ts-ignore
317      funcVaddrLastItem.libName = lastCallChain.fileName;
318      // @ts-ignore
319      sampleCallChainList.push(funcVaddrLastItem);
320    }
321
322    self.postMessage({
323      //@ts-ignore
324      id: data.id,
325      //@ts-ignore
326      action: data.action,
327      results: sampleCallChainList,
328    });
329  }
330
331  private perfAction(data: unknown): void {
332    //@ts-ignore
333    const params = data.params;
334    if (params) {
335      let filter = params.filter((item: { funcName: string }): boolean => item.funcName === 'getCurrentDataFromDbBottomUp' ||
336        item.funcName === 'getCurrentDataFromDbProfile' || item.funcName === 'getCurrentDataFromDbAnalysis');
337      let libFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'showLibLevelData');
338      let funFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'showFunLevelData');
339      if (libFilter.length !== 0) {
340        this.setLib(libFilter);
341      }
342      if (funFilter.length !== 0) {
343        this.setSymbol(funFilter);
344      }
345      if (filter.length === 0) {
346        let result = this.calReturnData(params);
347        self.postMessage({
348          //@ts-ignore
349          id: data.id,
350          //@ts-ignore
351          action: data.action,
352          results: result,
353        });
354      } else {
355        this.resolvingAction(params);
356      }
357    }
358  }
359
360  private perfGetVaddr(data: unknown): void {
361    // @ts-ignore
362    const params = data.params;
363    this.backVaddrData(data);
364  }
365
366  backVaddrData(data: unknown): void {
367    // @ts-ignore
368    this.handleDataByFuncName(data.params[0].funcName, data.params[0].funcArgs);
369  }
370
371  private perfReset(): void {
372    this.isHideThread = false;
373    this.isHideThreadState = false;
374    this.isTopDown = true;
375    this.isOnlyKernel = false;
376  }
377
378  private perfAsync(data: unknown): void {
379    //@ts-ignore
380    if (data.params.list) {
381      // 若前端存储过调用栈信息与被调用栈信息,可考虑从此处一起返回给主线程
382      //@ts-ignore
383      let arr = convertJSON(data.params.list) || [];
384      //@ts-ignore
385      let result = dealAsyncData(arr, this.callChainData, this.dataCache.nmHeapFrameMap, this.dataCache.dataDict, this.searchValue);
386      this.searchValue = '';
387      self.postMessage({
388        //@ts-ignore
389        id: data.id,
390        //@ts-ignore
391        action: data.action,
392        results: result,
393      });
394      arr = [];
395      result = [];
396    } else {
397      //@ts-ignore
398      this.searchValue = data.params.searchValue.toLocaleLowerCase();
399      //@ts-ignore
400      this.queryPerfAsync(data.params);
401    }
402  }
403
404  private setLib(libFilter: unknown): void {
405    this.lib = {
406      //@ts-ignore
407      libId: libFilter[0].funcArgs[0],
408      //@ts-ignore
409      libName: libFilter[0].funcArgs[1],
410    };
411  }
412  private setSymbol(funFilter: unknown): void {
413    this.symbol = {
414      //@ts-ignore
415      symbolId: funFilter[0].funcArgs[0],
416      //@ts-ignore
417      symbolName: funFilter[0].funcArgs[1],
418    };
419  }
420  private calReturnData(params: unknown): Array<unknown> {
421    let result: unknown[];
422    //@ts-ignore
423    let callChainsFilter = params.filter(
424      (item: { funcName: string }): boolean => item.funcName === 'getCallChainsBySampleIds'
425    );
426    callChainsFilter.length > 0 ? (this.isTopDown = callChainsFilter[0].funcArgs[0]) : (this.isTopDown = true);
427    //@ts-ignore
428    let isHideSystemSoFilter = params.filter(
429      (item: { funcName: string }): boolean => item.funcName === 'hideSystemLibrary'
430    );
431    //@ts-ignore
432    let hideThreadFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'hideThread');
433    //@ts-ignore
434    let hideThreadStateFilter = params.filter(
435      (item: { funcName: string }): boolean => item.funcName === 'hideThreadState'
436    );
437    //@ts-ignore
438    let onlyKernelFilter = [true];
439    if (this.lib) {
440      if (
441        callChainsFilter.length > 0 ||
442        isHideSystemSoFilter.length > 0 ||
443        hideThreadFilter.length > 0 ||
444        hideThreadStateFilter.length > 0 ||
445        onlyKernelFilter.length > 0
446      ) {
447        this.samplesData = this.combineCallChainForAnalysis(this.lib);
448        //@ts-ignore
449        result = this.resolvingAction(params);
450      } else {
451        let libData = this.combineCallChainForAnalysis(this.lib);
452        this.freshPerfCallchains(libData, this.isTopDown);
453        result = this.allProcess;
454        this.lib = undefined;
455      }
456    } else if (this.symbol) {
457      if (
458        callChainsFilter.length > 0 ||
459        isHideSystemSoFilter.length > 0 ||
460        hideThreadFilter.length > 0 ||
461        hideThreadStateFilter.length > 0 ||
462        onlyKernelFilter.length > 0
463      ) {
464        this.samplesData = this.combineCallChainForAnalysis(this.symbol);
465        //@ts-ignore
466        result = this.resolvingAction(params);
467      } else {
468        let funData = this.combineCallChainForAnalysis(this.symbol);
469        this.freshPerfCallchains(funData, this.isTopDown);
470        result = this.allProcess;
471        this.symbol = undefined;
472      }
473    } else {
474      //@ts-ignore
475      result = this.resolvingAction(params);
476    }
477    return result;
478  }
479  initPerfFiles(): void {
480    this.clearAll();
481    this.queryData(
482      this.currentEventId,
483      'perf-queryPerfFiles',
484      `select file_id as fileId, symbol, path
485       from perf_files`,
486      {}
487    );
488  }
489
490  initPerfThreads(): void {
491    this.queryData(
492      this.currentEventId,
493      'perf-queryPerfThread',
494      `select a.thread_id as tid, a.thread_name as threadName, a.process_id as pid, b.thread_name as processName
495       from perf_thread a
496                left join (select * from perf_thread where thread_id = process_id) b on a.process_id = b.thread_id`,
497      {}
498    );
499  }
500
501  initPerfCalls(): void {
502    this.queryData(
503      this.currentEventId,
504      'perf-queryPerfCalls',
505      `select count(callchain_id) as depth, callchain_id as sampleId, name
506       from perf_callchain
507       where callchain_id != -1
508       group by callchain_id`,
509      {}
510    );
511  }
512
513  initPerfCallchains(): void {
514    this.queryData(
515      this.currentEventId,
516      'perf-queryPerfCallchains',
517      `select c.name,
518              c.callchain_id  as sampleId,
519              c.vaddr_in_file as vaddrInFile,
520              c.offset_to_vaddr as offsetToVaddr,
521              c.file_id       as fileId,
522              c.depth,
523              c.symbol_id     as symbolId,
524              c.source_file_id as sourceFileId,
525              c.line_number as lineNumber
526       from perf_callchain c
527       where callchain_id != -1;`,
528      {}
529    );
530  }
531
532  queryPerfAsync(args: unknown): void {
533    let str: string = ``;
534    //@ts-ignore
535    if (args.cpu.length > 0) {
536      //@ts-ignore
537      str += `or cpu_id in (${args.cpu.join(',')})`;
538    }
539    //@ts-ignore
540    if (args.tid.length > 0) {
541      //@ts-ignore
542      str += `or tid in (${args.tid.join(',')})`;
543    }
544    //@ts-ignore
545    if (args.pid.length > 0) {
546      //@ts-ignore
547      str += `or process_id in (${args.pid.join(',')})`;
548    }
549    str = str.slice(3);
550    let eventStr: string = ``;
551    //@ts-ignore
552    if (args.eventId) {
553      //@ts-ignore
554      eventStr = `AND eventTypeId = ${args.eventId}`;
555    }
556    this.queryData(this.currentEventId, 'perf-async', `
557    select
558      ts - R.start_ts as time,
559      traceid,
560      thread_id as tid,
561      process_id as pid,
562      caller_callchainid as callerCallchainid,
563      callee_callchainid as calleeCallchainid,
564      perf_sample_id as perfSampleId,
565      event_count as eventCount,
566      event_type_id as eventTypeId
567    from
568      perf_napi_async A, trace_range R
569    WHERE
570      (` + str + `)` + eventStr + `
571    AND
572      time between ${
573      //@ts-ignore
574      args.leftNs} and ${args.rightNs}
575    `, {});
576  }
577
578  /**
579   *
580   * @param selectionParam
581   * @param sql 从饼图进程或者线程层点击进入Perf Profile时传入
582   */
583  private getCurrentDataFromDb(selectionParam: SelectionParam, flag: string, sql?: string): void {
584    let filterSql = this.setFilterSql(selectionParam, sql);
585    this.queryData(
586      this.currentEventId,
587      `${flag}`,
588      `select p.callchain_id as sampleId,
589          p.thread_state as threadState,
590          p.thread_id    as tid,
591          p.count as count,
592          p.process_id   as pid,
593          p.event_count  as eventCount,
594          p.ts as ts,
595          p.event_type_id as eventTypeId
596      from (select callchain_id, s.thread_id, s.event_type_id, thread_state, process_id,
597                count(callchain_id) as count,SUM(event_count) as event_count,
598                group_concat(s.timestamp_trace - t.start_ts,',') as ts
599            from perf_sample s, trace_range t
600            left join perf_thread thread on s.thread_id = thread.thread_id
601            where timestamp_trace between ${selectionParam.leftNs} + t.start_ts
602            and ${selectionParam.rightNs} + t.start_ts
603            and callchain_id != -1
604            and s.thread_id != 0 ${filterSql}
605            group by callchain_id, s.thread_id, thread_state, process_id) p`,
606      {
607        $startTime: selectionParam.leftNs,
608        $endTime: selectionParam.rightNs,
609        $sql: filterSql,
610      }
611    );
612  }
613
614  private setFilterSql(selectionParam: SelectionParam, sql?: string): string {
615    let filterSql = '';
616    if (sql) {
617      const cpus = selectionParam.perfAll ? [] : selectionParam.perfCpus;
618      const cpuFilter = cpus.length > 0 ? ` and s.cpu_id in (${cpus.join(',')}) ` : '';
619      let arg = `${sql}${cpuFilter}`.substring(3);
620      filterSql = `and ${arg}`;
621    } else {
622      const cpus = selectionParam.perfAll ? [] : selectionParam.perfCpus;
623      const processes = selectionParam.perfAll ? [] : selectionParam.perfProcess;
624      const threads = selectionParam.perfAll ? [] : selectionParam.perfThread;
625      if (cpus.length !== 0 || processes.length !== 0 || threads.length !== 0) {
626        const cpuFilter = cpus.length > 0 ? `or s.cpu_id in (${cpus.join(',')}) ` : '';
627        const processFilter = processes.length > 0 ? `or thread.process_id in (${processes.join(',')}) ` : '';
628        const threadFilter = threads.length > 0 ? `or s.thread_id in (${threads.join(',')})` : '';
629        let arg = `${cpuFilter}${processFilter}${threadFilter}`.substring(3);
630        filterSql = ` and (${arg})`;
631      }
632    }
633    let eventTypeId = selectionParam.perfEventTypeId;
634    const eventTypeFilter = eventTypeId !== undefined ? ` and s.event_type_id = ${eventTypeId}` : '';
635    filterSql += eventTypeFilter;
636    return filterSql;
637  }
638
639  clearAll(): void {
640    this.filesData = {};
641    this.samplesData = [];
642    this.threadData = {};
643    this.callChainData = {};
644    this.splitMapData = {};
645    this.currentTreeMapData = {};
646    this.currentTreeList = [];
647    this.searchValue = '';
648    this.dataSource = [];
649    this.allProcess = [];
650    this.dataCache.clearPerf();
651  }
652
653  initPerfCallChainTopDown(callChains: PerfCallChain[]): void {
654    this.callChainData = {};
655    callChains.forEach((callChain: PerfCallChain, index: number): void => {
656      if (callChain.sourceFileId) {
657        const sourceFile = DataCache.getInstance().dataDict.get(callChain.sourceFileId) || '';
658        const symbolName = DataCache.getInstance().dataDict.get(callChain.name as number) || '';
659        let lines = this.lineMap.get(`${sourceFile}_${symbolName}`);
660        if (lines === undefined) {
661          lines = new Set<number>();
662          this.lineMap.set(`${sourceFile}_${symbolName}`, lines);
663        }
664        lines.add(callChain.lineNumber);
665      }
666      this.setPerfCallChainFrameName(callChain);
667      this.addPerfGroupData(callChain);
668      let callChainDatum = this.callChainData[callChain.sampleId];
669      if (callChainDatum.length > 1) {
670        PerfCallChain.setNextNode(callChainDatum[callChainDatum.length - 2], callChainDatum[callChainDatum.length - 1]);
671      }
672    });
673  }
674
675  setPerfCallChainFrameName(callChain: PerfCallChain): void {
676    //设置调用栈的名称
677    callChain.canCharge = true;
678    if (this.filesData[callChain.fileId] && this.filesData[callChain.fileId].length > 0) {
679      callChain.fileName = this.filesData[callChain.fileId][0].fileName;
680      callChain.path = this.filesData[callChain.fileId][0].path;
681    } else {
682      callChain.fileName = 'unknown';
683    }
684  }
685
686  addPerfGroupData(callChain: PerfCallChain): void {
687    const currentCallChain = this.callChainData[callChain.sampleId] || [];
688    this.callChainData[callChain.sampleId] = currentCallChain;
689    if (currentCallChain.length > maxDepth) {
690      currentCallChain.splice(0, 1);
691    }
692    currentCallChain.push(callChain);
693  }
694
695  addOtherCallchainsData(countSample: PerfCountSample, list: PerfCallChain[]): void {
696    let threadCallChain = new PerfCallChain(); //新增的线程数据
697    threadCallChain.tid = countSample.tid;
698    threadCallChain.canCharge = false;
699    threadCallChain.isThread = true;
700    threadCallChain.name = `${this.threadData[countSample.tid].threadName || 'Thread'}(${countSample.tid})`;
701    let threadStateCallChain = new PerfCallChain(); //新增的线程状态数据
702    threadStateCallChain.tid = countSample.tid;
703    threadStateCallChain.isThreadState = true;
704    threadStateCallChain.name = countSample.threadState || 'Unknown State';
705    threadStateCallChain.fileName = threadStateCallChain.name === '-' ? 'Unknown Thread State' : '';
706    threadStateCallChain.canCharge = false;
707    if (!this.isHideThreadState) {
708      list.unshift(threadStateCallChain);
709    }
710    if (!this.isHideThread) {
711      list.unshift(threadCallChain);
712    }
713
714    if (this.isOnlyKernel) {
715      const flag = '[kernel.kallsyms]';
716      const newList = list.filter(i => i.fileName === flag || i.path === flag);
717      list.splice(0);
718      list.push(...newList);
719    }
720  }
721
722  private freshPerfCallchains(perfCountSamples: PerfCountSample[], isTopDown: boolean): void {
723    this.currentTreeMapData = {};
724    this.currentTreeList = [];
725    let totalSamplesCount = 0;
726    let totalEventCount = 0;
727    perfCountSamples.forEach((perfSample): void => {
728      totalSamplesCount += perfSample.count;
729      totalEventCount += perfSample.eventCount;
730      if (this.callChainData[perfSample.sampleId] && this.callChainData[perfSample.sampleId].length > 0) {
731        let perfCallChains = [...this.callChainData[perfSample.sampleId]];
732        this.addOtherCallchainsData(perfSample, perfCallChains);
733        let topIndex = isTopDown ? 0 : perfCallChains.length - 1;
734        if (perfCallChains.length > 0) {
735          let symbolName = '';
736          if (typeof perfCallChains[topIndex].name === 'number') {
737            //@ts-ignore
738            symbolName = this.dataCache.dataDict.get(perfCallChains[topIndex].name) || '';
739          } else {
740            //@ts-ignore
741            symbolName = perfCallChains[topIndex].name;
742          }
743          // 只展示内核栈合并进程栈
744          const usePidAsKey = this.isOnlyKernel ? '' : perfSample.pid;
745          let perfRootNode = this.currentTreeMapData[symbolName + usePidAsKey];
746          if (perfRootNode === undefined) {
747            perfRootNode = new PerfCallChainMerageData();
748            this.currentTreeMapData[symbolName + usePidAsKey] = perfRootNode;
749            this.currentTreeList.push(perfRootNode);
750          }
751          PerfCallChainMerageData.merageCallChainSample(perfRootNode, perfCallChains[topIndex], perfSample, false, this.lineMap);
752          this.mergeChildrenByIndex(perfRootNode, perfCallChains, topIndex, perfSample, isTopDown);
753        }
754      }
755    });
756    let rootMerageMap = this.mergeNodeData(totalEventCount, totalSamplesCount);
757    this.handleCurrentTreeList(totalEventCount, totalSamplesCount);
758    this.allProcess = Object.values(rootMerageMap);
759    // 浅拷贝
760    this.forkAllProcess = this.allProcess.slice();
761  }
762  private mergeNodeData(totalEventCount: number, totalSamplesCount: number): MergeMap {
763    // 只展示内核栈不添加进程这一级的结构
764    if (this.isOnlyKernel) {
765      return this.currentTreeMapData;
766    }
767    // 添加进程级结构
768    let rootMerageMap: MergeMap = {};
769    // @ts-ignore
770    Object.values(this.currentTreeMapData).forEach((merageData: PerfCallChainMerageData): void => {
771      if (rootMerageMap[merageData.pid] === undefined) {
772        let perfProcessMerageData = new PerfCallChainMerageData(); //新增进程的节点数据
773        perfProcessMerageData.canCharge = false;
774        perfProcessMerageData.symbolName =
775          (this.threadData[merageData.tid].processName || 'Process') + `(${merageData.pid})`;
776        perfProcessMerageData.isProcess = true;
777        perfProcessMerageData.symbol = perfProcessMerageData.symbolName;
778        perfProcessMerageData.tid = merageData.tid;
779        perfProcessMerageData.children.push(merageData);
780        perfProcessMerageData.initChildren.push(merageData);
781        perfProcessMerageData.dur = merageData.dur;
782        perfProcessMerageData.count = merageData.dur;
783        perfProcessMerageData.eventCount = merageData.eventCount;
784        perfProcessMerageData.total = totalSamplesCount;
785        perfProcessMerageData.totalEvent = totalEventCount;
786        perfProcessMerageData.tsArray = [...merageData.tsArray];
787        rootMerageMap[merageData.pid] = perfProcessMerageData;
788      } else {
789        rootMerageMap[merageData.pid].children.push(merageData);
790        rootMerageMap[merageData.pid].initChildren.push(merageData);
791        rootMerageMap[merageData.pid].dur += merageData.dur;
792        rootMerageMap[merageData.pid].count += merageData.dur;
793        rootMerageMap[merageData.pid].eventCount += merageData.eventCount;
794        rootMerageMap[merageData.pid].total = totalSamplesCount;
795        rootMerageMap[merageData.pid].totalEvent = totalEventCount;
796        for (const ts of merageData.tsArray) {
797          rootMerageMap[merageData.pid].tsArray.push(ts);
798        }
799      }
800      merageData.parentNode = rootMerageMap[merageData.pid]; //子节点添加父节点的引用
801    });
802    return rootMerageMap;
803  }
804  private handleCurrentTreeList(totalEventCount: number, totalSamplesCount: number): void {
805    let id = 0;
806    this.currentTreeList.forEach((perfTreeNode: PerfCallChainMerageData): void => {
807      perfTreeNode.total = totalSamplesCount;
808      perfTreeNode.totalEvent = totalEventCount;
809      if (perfTreeNode.id === '') {
810        perfTreeNode.id = id + '';
811        id++;
812      }
813      if (perfTreeNode.parentNode) {
814        if (perfTreeNode.parentNode.id === '') {
815          perfTreeNode.parentNode.id = id + '';
816          id++;
817        }
818        perfTreeNode.parentId = perfTreeNode.parentNode.id;
819      }
820    });
821  }
822
823  mergeChildrenByIndex(
824    currentNode: PerfCallChainMerageData,
825    callChainDataList: PerfCallChain[],
826    index: number,
827    sample: PerfCountSample,
828    isTopDown: boolean
829  ): void {
830    if ((isTopDown && index >= callChainDataList.length - 1) || (!isTopDown && index <= 0)) {
831      return;
832    }
833    isTopDown ? index++ : index--;
834    let isEnd = isTopDown ? callChainDataList.length === index + 1 : index === 0;
835    let node: PerfCallChainMerageData;
836    if (
837      currentNode.initChildren.filter((child: PerfCallChainMerageData): boolean => {
838        let name: number | string | undefined = callChainDataList[index].name;
839        if (typeof name === 'number') {
840          name = this.dataCache.dataDict.get(name);
841        }
842        if (child.symbolName === name) {
843          node = child;
844          PerfCallChainMerageData.merageCallChainSample(child, callChainDataList[index], sample, isEnd, this.lineMap);
845          return true;
846        }
847        return false;
848      }).length === 0
849    ) {
850      node = new PerfCallChainMerageData();
851      PerfCallChainMerageData.merageCallChainSample(node, callChainDataList[index], sample, isEnd, this.lineMap);
852      currentNode.children.push(node);
853      currentNode.initChildren.push(node);
854      this.currentTreeList.push(node);
855      node.parentNode = currentNode;
856    }
857    if (node! && !isEnd) {
858      this.mergeChildrenByIndex(node, callChainDataList, index, sample, isTopDown);
859    }
860  }
861
862  //所有的操作都是针对整个树结构的 不区分特定的数据
863  splitPerfTree(samples: PerfCallChainMerageData[], name: string, isCharge: boolean, isSymbol: boolean): void {
864    samples.forEach((process: PerfCallChainMerageData): void => {
865      process.children = [];
866      if (isCharge) {
867        this.recursionPerfChargeInitTree(process, name, isSymbol);
868      } else {
869        this.recursionPerfPruneInitTree(process, name, isSymbol);
870      }
871    });
872    this.resetAllNode(samples);
873  }
874
875  recursionPerfChargeInitTree(sample: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void {
876    if ((isSymbol && sample.symbolName === symbolName) || (!isSymbol && sample.libName === symbolName)) {
877      (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(sample);
878      sample.isStore++;
879    }
880    if (sample.initChildren.length > 0) {
881      sample.initChildren.forEach((child: PerfCallChainMerageData): void => {
882        this.recursionPerfChargeInitTree(child, symbolName, isSymbol);
883      });
884    }
885  }
886
887  recursionPerfPruneInitTree(node: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void {
888    if ((isSymbol && node.symbolName === symbolName) || (!isSymbol && node.libName === symbolName)) {
889      (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(node);
890      node.isStore++;
891      this.pruneChildren(node, symbolName);
892    } else if (node.initChildren.length > 0) {
893      node.initChildren.forEach((child): void => {
894        this.recursionPerfPruneInitTree(child, symbolName, isSymbol);
895      });
896    }
897  }
898
899  //symbol lib prune
900  recursionPruneTree(sample: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void {
901    if ((isSymbol && sample.symbolName === symbolName) || (!isSymbol && sample.libName === symbolName)) {
902      sample.parent && sample.parent.children.splice(sample.parent.children.indexOf(sample), 1);
903    } else {
904      sample.children.forEach((child: PerfCallChainMerageData): void => {
905        this.recursionPruneTree(child, symbolName, isSymbol);
906      });
907    }
908  }
909
910  recursionChargeByRule(
911    sample: PerfCallChainMerageData,
912    ruleName: string,
913    rule: (node: PerfCallChainMerageData) => boolean
914  ): void {
915    if (sample.initChildren.length > 0) {
916      sample.initChildren.forEach((child): void => {
917        if (rule(child) && !child.isThread && !child.isState) {
918          (this.splitMapData[ruleName] = this.splitMapData[ruleName] || []).push(child);
919          child.isStore++;
920        }
921        this.recursionChargeByRule(child, ruleName, rule);
922      });
923    }
924  }
925
926  pruneChildren(sample: PerfCallChainMerageData, symbolName: string): void {
927    if (sample.initChildren.length > 0) {
928      sample.initChildren.forEach((child: PerfCallChainMerageData): void => {
929        child.isStore++;
930        (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(child);
931        this.pruneChildren(child, symbolName);
932      });
933    }
934  }
935
936  hideSystemLibrary(): void {
937    this.allProcess.forEach((item: PerfCallChainMerageData): void => {
938      item.children = [];
939      this.recursionChargeByRule(item, systemRuleName, (node: PerfCallChainMerageData): boolean => {
940        return node.path.startsWith(systemRuleName);
941      });
942    });
943  }
944
945  hideNumMaxAndMin(startNum: number, endNum: string): void {
946    let max = endNum === '∞' ? Number.POSITIVE_INFINITY : parseInt(endNum);
947    this.allProcess.forEach((item: PerfCallChainMerageData): void => {
948      item.children = [];
949      // only kernel模式下第0层结构为调用栈,也需要变化
950      if (this.isOnlyKernel && (item.dur < startNum || item.dur > max)) {
951        (this.splitMapData[numRuleName] = this.splitMapData[numRuleName] || []).push(item);
952        item.isStore++;
953      }
954      this.recursionChargeByRule(item, numRuleName, (node: PerfCallChainMerageData): boolean => {
955        return node.dur < startNum || node.dur > max;
956      });
957    });
958  }
959
960  clearSplitMapData(symbolName: string): void {
961    if (symbolName in this.splitMapData) {
962      Reflect.deleteProperty(this.splitMapData, symbolName);
963    }
964  }
965
966  resetAllSymbol(symbols: string[]): void {
967    // 浅拷贝取出备份数据
968    this.allProcess = this.forkAllProcess.slice();
969    symbols.forEach((symbol: string): void => {
970      let list = this.splitMapData[symbol];
971      if (list !== undefined) {
972        list.forEach((item: PerfCallChainMerageData): void => {
973          item.isStore--;
974        });
975      }
976    });
977  }
978
979
980  clearSearchNode(): void {
981    this.currentTreeList.forEach((sample: PerfCallChainMerageData): void => {
982      sample.searchShow = true;
983      sample.isSearch = false;
984    });
985  }
986
987  resetAllNode(sample: PerfCallChainMerageData[]): void {
988    this.allProcess = this.forkAllProcess.slice();
989    if (this.isOnlyKernel) {
990      this.markSearchNode(this.allProcess, this.searchValue, false);
991      this.resetNewAllNode(sample);
992    } else {
993      this.clearSearchNode();
994      sample.forEach((process: PerfCallChainMerageData): void => {
995        process.searchShow = true;
996        process.isSearch = false;
997      });
998      this.resetNewAllNode(sample);
999      this.searchValue = this.searchValue.toLocaleLowerCase();
1000      let researchValue = this.searchValue.substring(1, this.searchValue.length);
1001      /*
1002        *开头,正则表达式
1003        非*开头,普通筛选
1004        '!'开头,普通反选
1005      */
1006      if (this.searchValue[0] === '!') {
1007        this.reMarkSearchNode(sample, researchValue, true);
1008      } else {
1009        if (this.searchValue[0] === '*') {
1010          // 正则反选
1011          if (this.searchValue[1] === '^') {
1012            this.markUnRegexSearchNode(this.allProcess, researchValue, true);
1013          } else {
1014            // 正则正选
1015            this.markRegexSearchNode(this.allProcess, researchValue, false);
1016          }
1017        } else {
1018          // 普通正选
1019          this.markSearchNode(sample, this.searchValue, false);
1020        }
1021      }
1022      this.resetNewAllNode(sample);
1023    }
1024  }
1025
1026  /**
1027   * 重置所有节点的子节点列表,并根据条件重新构建子节点列表
1028   * 此函数旨在清理和重新组织 PerfCallChainMerageData 类型的样本数组中的节点关系
1029   * 它通过移除某些节点并重新分配子节点来更新树结构
1030   *
1031   * @param sampleArray - lastShowNode
1032   */
1033  resetNewAllNode(sampleArray: PerfCallChainMerageData[]): void {
1034    sampleArray.forEach((process: PerfCallChainMerageData): void => {
1035      process.children = [];
1036    });
1037    let values = this.currentTreeList.map((item: PerfCallChainMerageData): PerfCallChainMerageData => {
1038      item.children = [];
1039      return item;
1040    });
1041    // 记录待删除的节点索引
1042    const removeList: number[] = [];
1043    // 用于记录所有有效的子节点
1044    const effectChildList: PerfCallChainMerageData[] = [];
1045    for (const sample of values) {
1046      if (sample.parentNode !== undefined && sample.isStore === 0 && sample.searchShow) {
1047        let parentNode = sample.parentNode;
1048        while (parentNode !== undefined && !(parentNode.isStore === 0 && parentNode.searchShow)) {
1049          parentNode = parentNode.parentNode!;
1050        }
1051        if (parentNode) {
1052          sample.parent = parentNode;
1053          parentNode.children.push(sample);
1054        }
1055      } else {
1056        // 如果节点没有父节点且是存储节点或者搜索不显示,则将其标记为待删除,并检查其子节点中是否有需要保留的
1057        if (!sample.parentNode && (sample.isStore > 0 || !sample.searchShow)) {
1058          removeList.push(this.allProcess.indexOf(sample));
1059          const effectChildren = this.findEffectChildren(sample.initChildren);
1060          if (effectChildren.length > 0) {
1061            effectChildList.push(...effectChildren);
1062          }
1063        }
1064      }
1065    }
1066    // 删除标记为待删除的节点
1067    removeList.sort((a, b) => b - a).forEach(index => {
1068      if (index >= 0 && index < values.length) {
1069        this.allProcess.splice(index, 1);
1070      }
1071    });
1072    // 将所有有效的子节点添加到返回的列表中
1073    this.allProcess.push(...effectChildList);
1074  }
1075
1076  /**
1077   * 递归查找调用链中非存储且标记为显示的子节点
1078   *
1079   * 本函数旨在筛选出调用链数据中,所有非存储(isStore为0)且被标记为需要显示(searchShow为true)的节点
1080   * 它通过递归遍历每个节点的子节点(initChildren),确保所有符合条件的节点都被找出
1081   *
1082   * @param children 调用链的子节点数组,这些子节点是待筛选的数据
1083   * @returns 返回一个新数组,包含所有非存储且标记为显示的子节点
1084   */
1085  private findEffectChildren(children: PerfCallChainMerageData[]): PerfCallChainMerageData[] {
1086    let result: PerfCallChainMerageData[] = [];
1087    for (let child of children) {
1088      // 如果搜索框有值,检查当前子树是否有任何一个节点的 isSearch 为 true
1089      if (this.searchValue === '' || this.hasSearchNode(child)) {
1090        // 如果当前节点非存储且需要显示,则直接添加到结果数组中
1091        if (child.isStore === 0 && child.searchShow) {
1092          result.push(child);
1093        } else {
1094          // 如果当前节点不符合条件,递归查找其子节点中符合条件的节点
1095          result.push(...this.findEffectChildren(child.initChildren));
1096        }
1097      }
1098    }
1099    // 返回结果数组
1100    return result;
1101  }
1102
1103  /**
1104   * 检查性能调用链合并数据中的节点是否包含搜索节点
1105   *
1106   * 该函数采用递归方式遍历节点及其子节点,以确定是否至少存在一个搜索节点
1107   * 主要用于在性能数据结构中快速定位是否有符合搜索条件的节点
1108   *
1109   * @param node 当前遍历的节点,类型为PerfCallChainMerageData
1110   * @returns 如果找到搜索节点则返回true,否则返回false
1111   */
1112  private hasSearchNode(node: PerfCallChainMerageData): boolean {
1113    if (node.isSearch) {
1114      return true;
1115    }
1116    for (let child of node.initChildren) {
1117      if (this.hasSearchNode(child)) {
1118        return true;
1119      }
1120    }
1121    return false;
1122  }
1123
1124  kernelCombination(): void {
1125    function mergeChildren(item: PerfCallChainMerageData): void {
1126      if (item.children.length <= 0) {
1127        return;
1128      }
1129      item.children = item.children.reduce((total: PerfCallChainMerageData[], pfcall: PerfCallChainMerageData): PerfCallChainMerageData[] => {
1130        for (const prev of total) {
1131          if (pfcall.symbol === prev.symbol) {
1132            prev.children.push(...pfcall.children);
1133            prev.total += pfcall.total;
1134            prev.count += pfcall.count;
1135            prev.totalEvent += pfcall.totalEvent;
1136            prev.eventCount += pfcall.eventCount;
1137            return total;
1138          }
1139        }
1140        total.push(pfcall);
1141        return total;
1142      }, [] as PerfCallChainMerageData[]);
1143      for (const child of item.children) {
1144        mergeChildren(child);
1145      }
1146    }
1147    this.allProcess.forEach((item: PerfCallChainMerageData): void => {
1148      mergeChildren(item);
1149    });
1150  }
1151
1152  // 普通正向筛选
1153  markSearchNode(sampleArray: PerfCallChainMerageData[], search: string, parentSearch: boolean): void {
1154    for (const sample of sampleArray) {
1155      if (search === '') {
1156        sample.searchShow = true;
1157        sample.isSearch = false;
1158        // 将反选标记全部清除
1159        sample.hiddenArray = [];
1160        sample.isReverseFilter = false;
1161      } else {
1162        let isInclude = sample.symbol.toLocaleLowerCase().includes(search);
1163        if ((sample.symbol && isInclude) || parentSearch) {
1164          sample.searchShow = true;
1165          sample.isSearch = sample.symbol !== undefined && isInclude;
1166          let parentNode = sample.parent;
1167          // 如果匹配,所有parent都显示
1168          while (parentNode !== undefined && !parentNode.searchShow) {
1169            parentNode.searchShow = true;
1170            parentNode = parentNode.parent;
1171          }
1172        } else {
1173          sample.searchShow = false;
1174          sample.isSearch = false;
1175        }
1176      }
1177      const children = this.isOnlyKernel ? sample.initChildren : sample.children;
1178      if (children.length > 0) {
1179        this.markSearchNode(children, search, sample.searchShow);
1180      }
1181    }
1182  }
1183
1184  reMarkSearchNode(sampleArray: PerfCallChainMerageData[], search: string, parentSearch: boolean): void {
1185    for (const sample of sampleArray) {
1186      if (search === '') {
1187        sample.searchShow = true;
1188        sample.isReverseFilter = false;
1189      } else {
1190        // 反选符合要求的
1191        let isInclude = !sample.symbol.toLocaleLowerCase().includes(search);
1192        // 从上往下遍历,父节点不展示的直接不展示,并且标记为置为false
1193        if (!parentSearch) {
1194          sample.searchShow = false
1195        } else {
1196          // 父节点展示并符合要求的直接展示
1197          if (sample.symbol && isInclude) {
1198            sample.isReverseFilter = true;
1199            sample.searchShow = true;
1200          } else {
1201            // 父节点展示子节点不展示的,反向向上遍历置false
1202            sample.isSearch = false;
1203            sample.searchShow = false;
1204            let parentNode = sample.parent
1205            parentNode?.hiddenArray.push(sample.symbolName);
1206            // 多个子节点,只有所有子节点都不展示的才可以不展示并且继续向上遍历
1207            while (parentNode && parentNode.children.length === parentNode.hiddenArray.length) {
1208              parentNode.searchShow = false;
1209              if (parentNode.parent?.hiddenArray.indexOf(parentNode.symbolName) === -1) {
1210                parentNode.parent?.hiddenArray.push(parentNode.symbolName)
1211              }
1212              parentNode = parentNode.parent;
1213            }
1214          }
1215        }
1216      }
1217      const children = this.isOnlyKernel ? sample.initChildren : sample.children;
1218      if (children.length > 0) {
1219        this.reMarkSearchNode(children, search, sample.searchShow);
1220      }
1221    }
1222  }
1223
1224  markUnRegexSearchNode(sampleArray: PerfCallChainMerageData[], reg: string, parentSearch: boolean): void {
1225    let regex = RegExp(reg);
1226    for (const sample of sampleArray) {
1227      // 反选符合要求的
1228      let isInclude = regex.test(sample.symbol.toLocaleLowerCase());
1229      if (!parentSearch) {
1230        sample.searchShow = false
1231      } else {
1232        if (sample.symbol && isInclude) {
1233          sample.isReverseFilter = true;
1234          sample.searchShow = true;
1235        } else {
1236          sample.isSearch = false;
1237          sample.searchShow = false;
1238          let parentNode = sample.parent
1239          parentNode?.hiddenArray.push(sample.symbolName);
1240          while (parentNode && parentNode.children.length === parentNode.hiddenArray.length) {
1241            parentNode.searchShow = false;
1242            if (parentNode.parent?.hiddenArray.indexOf(parentNode.symbolName) === -1) {
1243              parentNode.parent?.hiddenArray.push(parentNode.symbolName)
1244            }
1245            parentNode = parentNode.parent;
1246          }
1247        }
1248      }
1249      const children = this.isOnlyKernel ? sample.initChildren : sample.children;
1250      if (children.length > 0) {
1251        this.markUnRegexSearchNode(children, reg, sample.searchShow);
1252      }
1253    }
1254  }
1255
1256  markRegexSearchNode(sampleArray: PerfCallChainMerageData[], reg: string, parentSearch: boolean): void {
1257    let regex = RegExp(reg);
1258    for (const sample of sampleArray) {
1259      let isInclude = regex.test(sample.symbol.toLocaleLowerCase());
1260      if ((sample.symbol && isInclude) || parentSearch) {
1261        sample.searchShow = true;
1262        sample.isSearch = sample.symbol !== undefined && isInclude;
1263        let parentNode = sample.parent;
1264        // 如果匹配,所有parent都显示
1265        while (parentNode !== undefined && !parentNode.searchShow) {
1266          parentNode.searchShow = true;
1267          parentNode = parentNode.parent;
1268        }
1269      } else {
1270        sample.searchShow = false;
1271        sample.isSearch = false;
1272      }
1273
1274      const children = this.isOnlyKernel ? sample.initChildren : sample.children;
1275      if (children.length > 0) {
1276        this.markRegexSearchNode(children, reg, sample.searchShow);
1277      }
1278    }
1279  }
1280
1281  splitAllProcess(processArray: { select: string; name: string; type: string; checked: boolean }[]): void {
1282    processArray.forEach((item: { select: string; name: string; type: string; checked: boolean }): void => {
1283      this.allProcess.forEach((process): void => {
1284        if (item.select === '0') {
1285          this.recursionPerfChargeInitTree(process, item.name, item.type === 'symbol');
1286        } else {
1287          this.recursionPerfPruneInitTree(process, item.name, item.type === 'symbol');
1288        }
1289      });
1290      if (!item.checked) {
1291        this.resetAllSymbol([item.name]);
1292      }
1293    });
1294  }
1295  resolvingAction(params: unknown[]): unknown[] {
1296    if (params.length > 0) {
1297      for (let item of params) {
1298        //@ts-ignore
1299        if (item.funcName && item.funcArgs) {
1300          //@ts-ignore
1301          let result = this.handleDataByFuncName(item.funcName, item.funcArgs);
1302          if (result) {
1303            //@ts-ignore
1304            return result;
1305          }
1306        }
1307      }
1308      if (this.isOnlyKernel) {
1309        this.dataSource = this.allProcess;
1310      } else {
1311        this.dataSource = this.allProcess.filter((process: PerfCallChainMerageData): boolean => {
1312          return process.children && process.children.length > 0;
1313        });
1314      }
1315    }
1316    return this.dataSource;
1317  }
1318  private queryDataFromDb(funcArgs: unknown[], flag: string): void {
1319    if (funcArgs[1]) {
1320      let sql = '';
1321      //@ts-ignore
1322      if (funcArgs[1].processId !== undefined) {
1323        //@ts-ignore
1324        sql += `and thread.process_id = ${funcArgs[1].processId}`;
1325      }
1326      //@ts-ignore
1327      if (funcArgs[1].threadId !== undefined) {
1328        //@ts-ignore
1329        sql += ` and s.thread_id = ${funcArgs[1].threadId}`;
1330      }
1331      //@ts-ignore
1332      this.getCurrentDataFromDb(funcArgs[0], flag, sql);
1333    } else {
1334      //@ts-ignore
1335      this.getCurrentDataFromDb(funcArgs[0], flag, funcArgs[1]);
1336    }
1337  }
1338
1339  private queryVaddrToFile(funcArgs: unknown[]): void {
1340    if (funcArgs[1]) {
1341      let sql = '';
1342      //@ts-ignore
1343      if (funcArgs[1].processId !== undefined) {
1344        //@ts-ignore
1345        sql += `and thread.process_id = ${funcArgs[1].processId}`;
1346      }
1347      //@ts-ignore
1348      if (funcArgs[1].threadId !== undefined) {
1349        //@ts-ignore
1350        sql += ` and s.thread_id = ${funcArgs[1].threadId}`;
1351      }
1352      //@ts-ignore
1353      this.getVaddrToFile(funcArgs[0], sql);
1354    } else {
1355      //@ts-ignore
1356      this.getVaddrToFile(funcArgs[0]);
1357    }
1358  }
1359
1360  private getVaddrToFile(selectionParam: SelectionParam, sql?: string): void {
1361    let filterSql = this.setFilterSql(selectionParam, sql);
1362    this.queryData(
1363      this.currentEventId,
1364      'perf-vaddr-back',
1365      `select s.callchain_id,
1366            s.thread_id,
1367            thread.process_id,
1368            count(callchain_id) as count
1369            from perf_sample s, trace_range t
1370            left join perf_thread thread on s.thread_id = thread.thread_id
1371            where timestamp_trace between ${selectionParam.leftNs} + t.start_ts
1372            and ${selectionParam.rightNs} + t.start_ts
1373            and s.callchain_id != -1
1374            and s.thread_id != 0  ${filterSql}
1375        group by s.callchain_id,s.thread_id`,
1376      {
1377        $startTime: selectionParam.leftNs,
1378        $endTime: selectionParam.rightNs,
1379        $sql: filterSql,
1380      }
1381    );
1382  }
1383
1384  private handleDataByFuncName(funcName: string, funcArgs: unknown[]): unknown {
1385    let result;
1386    switch (funcName) {
1387      case 'getCallChainsBySampleIds':
1388        this.freshPerfCallchains(this.samplesData, funcArgs[0] as boolean);
1389        break;
1390      case 'getCurrentDataFromDbProfile':
1391        this.queryDataFromDb(funcArgs, 'perf-profile');
1392        break;
1393      case 'getCurrentDataFromDbAnalysis':
1394        this.queryDataFromDb(funcArgs, 'perf-analysis');
1395        break;
1396      case 'getCurrentDataFromDbBottomUp':
1397        this.queryDataFromDb(funcArgs, 'perf-bottomUp');
1398      case 'getVaddrToFile':
1399        this.queryVaddrToFile(funcArgs);
1400        break;
1401      case 'hideSystemLibrary':
1402        this.hideSystemLibrary();
1403        break;
1404      case 'hideThread':
1405        this.isHideThread = funcArgs[0] as boolean;
1406        break;
1407      case 'hideThreadState':
1408        this.isHideThreadState = funcArgs[0] as boolean;
1409        break;
1410      case 'onlyKernel':
1411        this.isOnlyKernel = funcArgs[0] as boolean;
1412        break;
1413      case 'hideNumMaxAndMin':
1414        this.hideNumMaxAndMin(funcArgs[0] as number, funcArgs[1] as string);
1415        break;
1416      case 'splitAllProcess':
1417        //@ts-ignore
1418        this.splitAllProcess(funcArgs[0]);
1419        break;
1420      case 'resetAllNode':
1421        this.resetAllNode(this.allProcess);
1422        break;
1423      case 'resotreAllNode':
1424        this.resetAllSymbol(funcArgs[0] as string[]);
1425        break;
1426      case 'clearSplitMapData':
1427        this.clearSplitMapData(funcArgs[0] as string);
1428        break;
1429      case 'splitTree':
1430        this.splitPerfTree(this.allProcess, funcArgs[0] as string, funcArgs[1] as boolean, funcArgs[2] as boolean);
1431        break;
1432      case 'setSearchValue':
1433        this.searchValue = funcArgs[0] as string;
1434        break;
1435
1436      case 'combineAnalysisCallChain':
1437        result = this.combineCallChainForAnalysis();
1438        break;
1439      case 'getBottomUp':
1440        result = this.getBottomUp();
1441        break;
1442      case 'kernelCombination':
1443        this.kernelCombination();
1444        break;
1445    }
1446    return result;
1447  }
1448
1449  combineCallChainForAnalysis(obj?: unknown): PerfAnalysisSample[] {
1450    let sampleCallChainList: Array<PerfAnalysisSample> = [];
1451    for (let sample of this.samplesData) {
1452      if (!this.callChainData[sample.sampleId]) {
1453        continue;
1454      }
1455      let callChains = [...this.callChainData[sample.sampleId]];
1456      const lastCallChain = callChains[callChains.length - 1];
1457      const threadName = this.threadData[sample.tid].threadName || 'Thread';
1458      const processName = this.threadData[sample.pid] ? this.threadData[sample.pid].threadName : 'Process';
1459      const funcName = this.dataCache.dataDict.get(lastCallChain.name as number);
1460      if (
1461        //@ts-ignore
1462        (obj && obj.libId === lastCallChain.fileId && obj.libName === lastCallChain.fileName) ||
1463        //@ts-ignore
1464        (obj && obj.symbolId === lastCallChain.symbolId && obj.symbolName === funcName) ||
1465        !obj
1466      ) {
1467        let analysisSample = new PerfAnalysisSample(
1468          threadName,
1469          lastCallChain.depth,
1470          lastCallChain.vaddrInFile,
1471          lastCallChain.offsetToVaddr,
1472          processName,
1473          lastCallChain.fileId,
1474          lastCallChain.fileName,
1475          lastCallChain.symbolId,
1476          this.dataCache.dataDict.get(lastCallChain.name as number) || ''
1477        );
1478        analysisSample.tid = sample.tid;
1479        analysisSample.pid = sample.pid;
1480        analysisSample.count = sample.count;
1481        analysisSample.threadState = sample.threadState;
1482        analysisSample.eventCount = sample.eventCount;
1483        analysisSample.sampleId = sample.sampleId;
1484        sampleCallChainList.push(analysisSample);
1485      }
1486    }
1487    return sampleCallChainList;
1488  }
1489
1490  getBottomUp(): PerfBottomUpStruct[] {
1491    const topUp = new PerfBottomUpStruct('topUp');
1492    let perfTime = 1;
1493    for (let sample of this.samplesData) {
1494      let currentNode = topUp;
1495      let callChains = this.callChainData[sample.sampleId];
1496      for (let i = 0; i < callChains.length; i++) {
1497        if (i === 0) {
1498          currentNode = topUp;
1499        }
1500        let item = callChains[i];
1501        const existingNode = currentNode.children.find(
1502          (child) => child.symbolName === `${item.name}(${item.fileName})`
1503        );
1504        if (existingNode) {
1505          existingNode.tsArray.push(...sample.ts.split(',').map(Number));
1506          currentNode = existingNode;
1507          existingNode.totalTime += perfTime * sample.count;
1508          existingNode.eventCount += sample.eventCount;
1509          existingNode.calculateSelfTime();
1510          existingNode.notifyParentUpdateSelfTime();
1511        } else {
1512          const symbolName = this.dataCache.dataDict.get(item.name as number) || '';
1513          let newNode = new PerfBottomUpStruct(`${symbolName}(${item.fileName})`);
1514          newNode.totalTime = perfTime * sample.count;
1515          newNode.eventCount = sample.eventCount;
1516          newNode.tsArray = sample.ts.split(',').map(Number);
1517          currentNode.addChildren(newNode);
1518          newNode.calculateSelfTime();
1519          newNode.notifyParentUpdateSelfTime();
1520          currentNode = newNode;
1521        }
1522      }
1523    }
1524    topUp.children.forEach((child: PerfBottomUpStruct): void => {
1525      child.parentNode = undefined;
1526    });
1527
1528    let date = this.topUpDataToBottomUpData(topUp.children);
1529    if (this.isPerfBottomUp) {
1530      this.isPerfBottomUp = false;
1531    }
1532    return date;
1533  }
1534
1535  private topUpDataToBottomUpData(perfPositiveArray: Array<PerfBottomUpStruct>): Array<PerfBottomUpStruct> {
1536    let reverseTreeArray: Array<PerfBottomUpStruct> = [];
1537    const recursionTree = (perfBottomUpStruct: PerfBottomUpStruct): void => {
1538      if (perfBottomUpStruct.selfTime > 0) {
1539        const clonePerfBottomUpStruct = new PerfBottomUpStruct(perfBottomUpStruct.symbolName);
1540        clonePerfBottomUpStruct.selfTime = perfBottomUpStruct.selfTime;
1541        clonePerfBottomUpStruct.totalTime = perfBottomUpStruct.totalTime;
1542        clonePerfBottomUpStruct.eventCount = perfBottomUpStruct.eventCount;
1543        clonePerfBottomUpStruct.tsArray = [...perfBottomUpStruct.tsArray];
1544        reverseTreeArray.push(clonePerfBottomUpStruct);
1545        this.copyParentNode(clonePerfBottomUpStruct, perfBottomUpStruct);
1546      }
1547      if (perfBottomUpStruct.children.length > 0) {
1548        for (const children of perfBottomUpStruct.children) {
1549          children.parentNode = perfBottomUpStruct;
1550          recursionTree(children);
1551        }
1552      }
1553    };
1554    for (const perfBottomUpStruct of perfPositiveArray) {
1555      recursionTree(perfBottomUpStruct);
1556    }
1557    return this.mergeTreeBifurcation(reverseTreeArray, null);
1558  }
1559
1560  private mergeTreeBifurcation(
1561    reverseTreeArray: Array<PerfBottomUpStruct> | null,
1562    parent: PerfBottomUpStruct | null
1563  ): Array<PerfBottomUpStruct> {
1564    const sameSymbolMap = new Map<string, PerfBottomUpStruct>();
1565    const currentLevelData: Array<PerfBottomUpStruct> = [];
1566    const dataArray = reverseTreeArray || parent?.frameChildren;
1567    if (!dataArray) {
1568      return [];
1569    }
1570    for (const perfBottomUpStruct of dataArray) {
1571      let symbolKey = perfBottomUpStruct.symbolName;
1572      let bottomUpStruct: PerfBottomUpStruct;
1573      if (sameSymbolMap.has(symbolKey)) {
1574        bottomUpStruct = sameSymbolMap.get(symbolKey)!;
1575        bottomUpStruct.totalTime += perfBottomUpStruct.totalTime;
1576        bottomUpStruct.selfTime += perfBottomUpStruct.selfTime;
1577        for (const ts of perfBottomUpStruct.tsArray) {
1578          bottomUpStruct.tsArray.push(ts);
1579        }
1580      } else {
1581        bottomUpStruct = perfBottomUpStruct;
1582        sameSymbolMap.set(symbolKey, bottomUpStruct);
1583        currentLevelData.push(bottomUpStruct);
1584        if (parent) {
1585          parent.addChildren(bottomUpStruct);
1586        }
1587      }
1588      bottomUpStruct.frameChildren?.push(...perfBottomUpStruct.children);
1589    }
1590
1591    for (const data of currentLevelData) {
1592      this.mergeTreeBifurcation(null, data);
1593      data.frameChildren = [];
1594    }
1595    if (reverseTreeArray) {
1596      return currentLevelData;
1597    } else {
1598      return [];
1599    }
1600  }
1601
1602  /**
1603   * copy整体调用链,从栈顶函数一直copy到栈底函数,
1604   * 给Parent设置selfTime,totalTime设置为children的selfTime,totalTime
1605   *  */
1606  private copyParentNode(perfBottomUpStruct: PerfBottomUpStruct, bottomUpStruct: PerfBottomUpStruct): void {
1607    if (bottomUpStruct.parentNode) {
1608      const copyParent = new PerfBottomUpStruct(bottomUpStruct.parentNode.symbolName);
1609      copyParent.selfTime = perfBottomUpStruct.selfTime;
1610      copyParent.totalTime = perfBottomUpStruct.totalTime;
1611      copyParent.eventCount = perfBottomUpStruct.eventCount;
1612      copyParent.tsArray = [...perfBottomUpStruct.tsArray];
1613      perfBottomUpStruct.addChildren(copyParent);
1614      this.copyParentNode(copyParent, bottomUpStruct.parentNode);
1615    }
1616  }
1617}
1618
1619export class PerfFile {
1620  fileId: number = 0;
1621  symbol: string = '';
1622  path: string = '';
1623  fileName: string = '';
1624
1625  static setFileName(data: PerfFile): void {
1626    if (data.path) {
1627      let number = data.path.lastIndexOf('/');
1628      if (number > 0) {
1629        data.fileName = data.path.substring(number + 1);
1630        return;
1631      }
1632    }
1633    data.fileName = data.path;
1634  }
1635
1636  setFileName(): void {
1637    if (this.path) {
1638      let number = this.path.lastIndexOf('/');
1639      if (number > 0) {
1640        this.fileName = this.path.substring(number + 1);
1641        return;
1642      }
1643    }
1644    this.fileName = this.path;
1645  }
1646}
1647
1648export class PerfThread {
1649  tid: number = 0;
1650  pid: number = 0;
1651  threadName: string = '';
1652  processName: string = '';
1653}
1654
1655export class PerfCallChain {
1656  startNS: number = 0;
1657  dur: number = 0;
1658  sampleId: number = 0;
1659  callChainId: number = 0;
1660  vaddrInFile: number = 0;
1661  offsetToVaddr: number = 0;
1662  tid: number = 0;
1663  pid: number = 0;
1664  name: number | string = 0;
1665  fileName: string = '';
1666  threadState: string = '';
1667  fileId: number = 0;
1668  symbolId: number = 0;
1669  path: string = '';
1670  count: number = 0;
1671  eventCount: number = 0;
1672  parentId: string = ''; //合并之后区分的id
1673  id: string = '';
1674  topDownMerageId: string = ''; //top down合并使用的id
1675  topDownMerageParentId: string = ''; //top down合并使用的id
1676  bottomUpMerageId: string = ''; //bottom up合并使用的id
1677  bottomUpMerageParentId: string = ''; //bottom up合并使用的id
1678  depth: number = 0;
1679  canCharge: boolean = true;
1680  previousNode: PerfCallChain | undefined = undefined; //将list转换为一个链表结构
1681  nextNode: PerfCallChain | undefined = undefined;
1682  isThread: boolean = false;
1683  isProcess: boolean = false;
1684  isThreadState: boolean = false;
1685  sourceFileId: number = 0;
1686  lineNumber: number = 0;
1687
1688  static setNextNode(currentNode: PerfCallChain, nextNode: PerfCallChain): void {
1689    currentNode.nextNode = nextNode;
1690    nextNode.previousNode = currentNode;
1691  }
1692
1693  static setPreviousNode(currentNode: PerfCallChain, prevNode: PerfCallChain): void {
1694    currentNode.previousNode = prevNode;
1695    prevNode.nextNode = currentNode;
1696  }
1697
1698  static merageCallChain(currentNode: PerfCallChain, callChain: PerfCallChain): void {
1699    currentNode.startNS = callChain.startNS;
1700    currentNode.tid = callChain.tid;
1701    currentNode.pid = callChain.pid;
1702    currentNode.sampleId = callChain.sampleId;
1703    currentNode.dur = callChain.dur;
1704    currentNode.count = callChain.count;
1705    currentNode.eventCount = callChain.eventCount;
1706  }
1707}
1708
1709export class PerfCallChainMerageData extends ChartStruct {
1710  // @ts-ignore
1711  #parentNode: PerfCallChainMerageData | undefined = undefined;
1712  // @ts-ignore
1713  #total = 0;
1714  // @ts-ignore
1715  #totalEvent = 0;
1716  id: string = '';
1717  parentId: string = '';
1718  parent: PerfCallChainMerageData | undefined = undefined;
1719  symbolName: string = '';
1720  symbol: string = '';
1721  libName: string = '';
1722  path: string = '';
1723  weight: string = '';
1724  weightPercent: string = '';
1725  selfDur: number = 0;
1726  dur: number = 0;
1727  tid: number = 0;
1728  pid: number = 0;
1729  isStore = 0;
1730  canCharge: boolean = true;
1731  children: PerfCallChainMerageData[] = [];
1732  initChildren: PerfCallChainMerageData[] = [];
1733  type: number = 0;
1734  vaddrInFile: number = 0;
1735  offsetToVaddr: number = 0;
1736  isSelected: boolean = false;
1737  searchShow: boolean = true;
1738  isSearch: boolean = false;
1739  isState: boolean = false;
1740  isReverseFilter: boolean = false;
1741  hiddenArray: Array<string> = [];
1742  set parentNode(data: PerfCallChainMerageData | undefined) {
1743    this.parent = data;
1744    this.#parentNode = data;
1745  }
1746
1747  get parentNode(): PerfCallChainMerageData | undefined {
1748    return this.#parentNode;
1749  }
1750
1751  set total(data: number) {
1752    this.#total = data;
1753    this.weight = `${this.dur}`;
1754    this.weightPercent = `${((this.dur / data) * 100).toFixed(1)}%`;
1755  }
1756
1757  get total(): number {
1758    return this.#total;
1759  }
1760
1761  set totalEvent(data: number) {
1762    this.#totalEvent = data;
1763    this.eventPercent = `${((this.eventCount / data) * 100).toFixed(1)}%`;
1764  }
1765
1766  get totalEvent(): number {
1767    return this.#totalEvent;
1768  }
1769
1770  static merageCallChainSample(
1771    currentNode: PerfCallChainMerageData,
1772    callChain: PerfCallChain,
1773    sample: PerfCountSample,
1774    isEnd: boolean,
1775    lineMap: Map<string, Set<number>>
1776  ): void {
1777    if (currentNode.symbolName === '') {
1778      let symbolName = '';
1779      if (typeof callChain.name === 'number') {
1780        symbolName = DataCache.getInstance().dataDict.get(callChain.name) || '';
1781      } else {
1782        symbolName = callChain.name;
1783      }
1784      currentNode.symbol = `${symbolName} ${callChain.fileName ? `(${callChain.fileName})` : ''}`;
1785      currentNode.symbolName = symbolName;
1786      currentNode.pid = sample.pid;
1787      currentNode.tid = sample.tid;
1788      currentNode.libName = callChain.fileName;
1789      currentNode.vaddrInFile = callChain.vaddrInFile;
1790      currentNode.offsetToVaddr = callChain.offsetToVaddr;
1791      currentNode.lib = callChain.fileName;
1792      currentNode.addr = `${'0x'}${callChain.vaddrInFile.toString(16)}`;
1793      currentNode.canCharge = callChain.canCharge;
1794      if (callChain.path) {
1795        currentNode.path = callChain.path;
1796      }
1797      if (callChain.sourceFileId) {
1798        currentNode.sourceFile = DataCache.getInstance().dataDict.get(callChain.sourceFileId) || '';
1799        const lines = lineMap.get(`${currentNode.sourceFile}_${currentNode.symbolName}`);
1800        if (lines) {
1801          currentNode.lineNumber = lines;
1802        }
1803      }
1804    }
1805    if (isEnd) {
1806      currentNode.selfDur += sample.count;
1807    }
1808    if (callChain.isThread && !currentNode.isThread) {
1809      currentNode.isThread = callChain.isThread;
1810    }
1811    if (callChain.isThreadState && !currentNode.isState) {
1812      currentNode.isState = callChain.isThreadState;
1813    }
1814    currentNode.dur += sample.count;
1815    currentNode.count += sample.count;
1816    currentNode.eventCount += sample.eventCount;
1817    currentNode.tsArray.push(...sample.ts.split(',').map(Number));
1818  }
1819}
1820
1821export class PerfCountSample {
1822  sampleId: number = 0;
1823  tid: number = 0;
1824  count: number = 0;
1825  threadState: string = '';
1826  pid: number = 0;
1827  eventCount: number = 0;
1828  ts: string = '';
1829}
1830
1831export class PerfStack {
1832  symbol: string = '';
1833  path: string = '';
1834  fileId: number = 0;
1835  type: number = 0;
1836  vaddrInFile: number = 0;
1837}
1838
1839export class PerfCmdLine {
1840  report_value: string = '';
1841}
1842
1843class PerfAnalysisSample extends PerfCountSample {
1844  threadName: string;
1845  depth: number;
1846  vaddr_in_file: number;
1847  offset_to_vaddr: number;
1848  processName: string;
1849  libId: number;
1850  libName: string;
1851  symbolId: number;
1852  symbolName: string;
1853
1854  constructor(
1855    threadName: string,
1856    depth: number,
1857    vaddr_in_file: number,
1858    offset_to_vaddr: number,
1859    processName: string,
1860    libId: number,
1861    libName: string,
1862    symbolId: number,
1863    symbolName: string
1864  ) {
1865    super();
1866    this.threadName = threadName;
1867    this.depth = depth;
1868    this.vaddr_in_file = vaddr_in_file;
1869    this.offset_to_vaddr = offset_to_vaddr;
1870    this.processName = processName;
1871    this.libId = libId;
1872    this.libName = libName;
1873    this.symbolId = symbolId;
1874    this.symbolName = symbolName;
1875  }
1876}
1877
1878export function timeMsFormat2p(ns: number): string {
1879  let currentNs = ns;
1880  let hour1 = 3600_000;
1881  let minute1 = 60_000;
1882  let second1 = 1_000; // 1 second
1883  let perfResult = '';
1884  if (currentNs >= hour1) {
1885    perfResult += `${Math.floor(currentNs / hour1).toFixed(2)}h`;
1886    return perfResult;
1887  }
1888  if (currentNs >= minute1) {
1889    perfResult += `${Math.floor(currentNs / minute1).toFixed(2)}min`;
1890    return perfResult;
1891  }
1892  if (currentNs >= second1) {
1893    perfResult += `${Math.floor(currentNs / second1).toFixed(2)}s`;
1894    return perfResult;
1895  }
1896  if (currentNs > 0) {
1897    perfResult += `${currentNs.toFixed(2)}ms`;
1898    return perfResult;
1899  }
1900  if (perfResult === '') {
1901    perfResult = '0s';
1902  }
1903  return perfResult;
1904}
1905
1906class HiPrefSample {
1907  name: string = '';
1908  depth: number = 0;
1909  callchain_id: number = 0;
1910  totalTime: number = 0;
1911  thread_id: number = 0;
1912  id: number = 0;
1913  eventCount: number = 0;
1914  startTime: number = 0;
1915  endTime: number = 0;
1916  timeTip: number = 0;
1917  cpu_id: number = 0;
1918  stack?: Array<HiPerfSymbol>;
1919}
1920