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