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
72  handle(data: unknown): void {
73    //@ts-ignore
74    this.currentEventId = data.id;
75    //@ts-ignore
76    if (data && data.type) {
77      //@ts-ignore
78      switch (data.type) {
79        case 'perf-init':
80          //@ts-ignore
81          this.dataCache.perfCountToMs = data.params.fValue;
82          this.initPerfFiles();
83          break;
84        case 'perf-queryPerfFiles':
85          //@ts-ignore
86          this.perfQueryPerfFiles(data.params.list);
87          break;
88        case 'perf-queryPerfThread':
89          //@ts-ignore
90          this.perfQueryPerfThread(data.params.list);
91          break;
92        case 'perf-queryPerfCalls':
93          //@ts-ignore
94          this.perfQueryPerfCalls(data.params.list);
95          break;
96        case 'perf-queryPerfCallchains':
97          this.perfQueryPerfCallchains(data);
98          break;
99        case 'perf-queryCallchainsGroupSample':
100          this.perfQueryCallchainsGroupSample(data);
101          break;
102        case 'perf-action':
103          this.perfAction(data);
104          break;
105        case 'perf-reset':
106          this.perfReset();
107          break;
108        case 'perf-async':
109          this.perfAsync(data);
110          break;
111      }
112    }
113  }
114  private perfQueryPerfFiles(list: Array<PerfFile>): void {
115    let files = convertJSON(list) || [];
116    //@ts-ignore
117    files.forEach((file: PerfFile) => {
118      this.filesData[file.fileId] = this.filesData[file.fileId] || [];
119      PerfFile.setFileName(file);
120      this.filesData[file.fileId].push(file);
121    });
122    this.initPerfThreads();
123  }
124  private perfQueryPerfThread(list: Array<PerfThread>): void {
125    let threads = convertJSON(list) || [];
126    //@ts-ignore
127    threads.forEach((thread: PerfThread): void => {
128      this.threadData[thread.tid] = thread;
129    });
130    this.initPerfCalls();
131  }
132  private perfQueryPerfCalls(list: Array<PerfCall>): void {
133    let perfCalls = convertJSON(list) || [];
134    if (perfCalls.length !== 0) {
135      //@ts-ignore
136      perfCalls.forEach((perfCall: PerfCall): void => {
137        this.dataCache.perfCallChainMap.set(perfCall.sampleId, perfCall);
138      });
139    }
140    this.initPerfCallchains();
141  }
142  private perfQueryPerfCallchains(data: unknown): void {
143    //@ts-ignore
144    let arr = convertJSON(data.params.list) || [];
145    this.initPerfCallChainTopDown(arr as PerfCallChain[]);
146
147    self.postMessage({
148      // @ts-ignore
149      id: data.id,
150      // @ts-ignore
151      action: data.action,
152      results: this.dataCache.perfCallChainMap,
153    });
154  }
155  private perfQueryCallchainsGroupSample(data: unknown): void {
156    //@ts-ignore
157    this.samplesData = convertJSON(data.params.list) || [];
158    let result;
159    if (this.isAnalysis) {
160      result = this.resolvingAction([
161        {
162          funcName: 'combineAnalysisCallChain',
163          funcArgs: [true],
164        },
165      ]);
166    } else if (this.isPerfBottomUp) {
167      result = this.resolvingAction([
168        {
169          funcName: 'getBottomUp',
170          funcArgs: [true],
171        },
172      ]);
173    } else {
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    }
193    self.postMessage({
194      //@ts-ignore
195      id: data.id,
196      //@ts-ignore
197      action: data.action,
198      results: result,
199    });
200    if (this.isAnalysis) {
201      this.isAnalysis = false;
202    }
203  }
204  private perfAction(data: unknown): void {
205    //@ts-ignore
206    const params = data.params;
207    if (params) {
208      let filter = params.filter((item: { funcName: string }): boolean => item.funcName === 'getCurrentDataFromDb');
209      let libFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'showLibLevelData');
210      let funFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'showFunLevelData');
211      if (libFilter.length !== 0) {
212        this.setLib(libFilter);
213      }
214      if (funFilter.length !== 0) {
215        this.setSymbol(funFilter);
216      }
217      if (filter.length === 0) {
218        let result = this.calReturnData(params);
219        self.postMessage({
220          //@ts-ignore
221          id: data.id,
222          //@ts-ignore
223          action: data.action,
224          results: result,
225        });
226      } else {
227        this.resolvingAction(params);
228      }
229    }
230  }
231  private perfReset(): void {
232    this.isHideThread = false;
233    this.isHideThreadState = false;
234    this.isTopDown = true;
235    this.isOnlyKernel = false;
236  }
237
238  private perfAsync(data: unknown): void {
239    //@ts-ignore
240    if (data.params.list) {
241      // 若前端存储过调用栈信息与被调用栈信息,可考虑从此处一起返回给主线程
242      //@ts-ignore
243      let arr = convertJSON(data.params.list) || [];
244      //@ts-ignore
245      let result = dealAsyncData(arr, this.callChainData, this.dataCache.nmHeapFrameMap, this.dataCache.dataDict, this.searchValue);
246      this.searchValue = '';
247      self.postMessage({
248        //@ts-ignore
249        id: data.id,
250        //@ts-ignore
251        action: data.action,
252        results: result,
253      });
254      arr = [];
255      result = [];
256    } else {
257      //@ts-ignore
258      this.searchValue = data.params.searchValue;
259      //@ts-ignore
260      this.queryPerfAsync(data.params);
261    }
262  }
263
264  private setLib(libFilter: unknown): void {
265    this.lib = {
266      //@ts-ignore
267      libId: libFilter[0].funcArgs[0],
268      //@ts-ignore
269      libName: libFilter[0].funcArgs[1],
270    };
271  }
272  private setSymbol(funFilter: unknown): void {
273    this.symbol = {
274      //@ts-ignore
275      symbolId: funFilter[0].funcArgs[0],
276      //@ts-ignore
277      symbolName: funFilter[0].funcArgs[1],
278    };
279  }
280  private calReturnData(params: unknown): Array<unknown> {
281    let result: unknown[];
282    //@ts-ignore
283    let callChainsFilter = params.filter(
284      (item: { funcName: string }): boolean => item.funcName === 'getCallChainsBySampleIds'
285    );
286    callChainsFilter.length > 0 ? (this.isTopDown = callChainsFilter[0].funcArgs[0]) : (this.isTopDown = true);
287    //@ts-ignore
288    let isHideSystemSoFilter = params.filter(
289      (item: { funcName: string }): boolean => item.funcName === 'hideSystemLibrary'
290    );
291    //@ts-ignore
292    let hideThreadFilter = params.filter((item: { funcName: string }): boolean => item.funcName === 'hideThread');
293    //@ts-ignore
294    let hideThreadStateFilter = params.filter(
295      (item: { funcName: string }): boolean => item.funcName === 'hideThreadState'
296    );
297    //@ts-ignore
298    let onlyKernelFilter = [true];
299    if (this.lib) {
300      if (
301        callChainsFilter.length > 0 ||
302        isHideSystemSoFilter.length > 0 ||
303        hideThreadFilter.length > 0 ||
304        hideThreadStateFilter.length > 0 ||
305        onlyKernelFilter.length > 0
306      ) {
307        this.samplesData = this.combineCallChainForAnalysis(this.lib);
308        //@ts-ignore
309        result = this.resolvingAction(params);
310      } else {
311        let libData = this.combineCallChainForAnalysis(this.lib);
312        this.freshPerfCallchains(libData, this.isTopDown);
313        result = this.allProcess;
314        this.lib = undefined;
315      }
316    } else if (this.symbol) {
317      if (
318        callChainsFilter.length > 0 ||
319        isHideSystemSoFilter.length > 0 ||
320        hideThreadFilter.length > 0 ||
321        hideThreadStateFilter.length > 0 ||
322        onlyKernelFilter.length > 0
323      ) {
324        this.samplesData = this.combineCallChainForAnalysis(this.symbol);
325        //@ts-ignore
326        result = this.resolvingAction(params);
327      } else {
328        let funData = this.combineCallChainForAnalysis(this.symbol);
329        this.freshPerfCallchains(funData, this.isTopDown);
330        result = this.allProcess;
331        this.symbol = undefined;
332      }
333    } else {
334      //@ts-ignore
335      result = this.resolvingAction(params);
336    }
337    return result;
338  }
339  initPerfFiles(): void {
340    this.clearAll();
341    this.queryData(
342      this.currentEventId,
343      'perf-queryPerfFiles',
344      `select file_id as fileId, symbol, path
345       from perf_files`,
346      {}
347    );
348  }
349
350  initPerfThreads(): void {
351    this.queryData(
352      this.currentEventId,
353      'perf-queryPerfThread',
354      `select a.thread_id as tid, a.thread_name as threadName, a.process_id as pid, b.thread_name as processName
355       from perf_thread a
356                left join (select * from perf_thread where thread_id = process_id) b on a.process_id = b.thread_id`,
357      {}
358    );
359  }
360
361  initPerfCalls(): void {
362    this.queryData(
363      this.currentEventId,
364      'perf-queryPerfCalls',
365      `select count(callchain_id) as depth, callchain_id as sampleId, name
366       from perf_callchain
367       where callchain_id != -1
368       group by callchain_id`,
369      {}
370    );
371  }
372
373  initPerfCallchains(): void {
374    this.queryData(
375      this.currentEventId,
376      'perf-queryPerfCallchains',
377      `select c.name,
378              c.callchain_id  as sampleId,
379              c.vaddr_in_file as vaddrInFile,
380              c.file_id       as fileId,
381              c.depth,
382              c.symbol_id     as symbolId
383       from perf_callchain c
384       where callchain_id != -1;`,
385      {}
386    );
387  }
388
389  queryPerfAsync(args: unknown): void {
390    let str: string = ``;
391    //@ts-ignore
392    if (args.cpu.length > 0) {
393      //@ts-ignore
394      str += `or cpu_id in (${args.cpu.join(',')})`;
395    }
396    //@ts-ignore
397    if (args.tid.length > 0) {
398      //@ts-ignore
399      str += `or tid in (${args.tid.join(',')})`;
400    }
401    //@ts-ignore
402    if (args.pid.length > 0) {
403      //@ts-ignore
404      str += `or process_id in (${args.pid.join(',')})`;
405    }
406    str = str.slice(3);
407    let eventStr: string = ``;
408    //@ts-ignore
409    if (args.eventId) {
410      //@ts-ignore
411      eventStr = `AND eventTypeId = ${args.eventId}`;
412    }
413    this.queryData(this.currentEventId, 'perf-async', `
414    select 
415      ts - R.start_ts as time,
416      traceid,
417      thread_id as tid,
418      process_id as pid,
419      caller_callchainid as callerCallchainid,
420      callee_callchainid as calleeCallchainid,
421      perf_sample_id as perfSampleId,
422      event_count as eventCount,
423      event_type_id as eventTypeId
424    from 
425      perf_napi_async A, trace_range R
426    WHERE 
427      (` + str + `)` + eventStr + `
428    AND
429      time between ${
430      //@ts-ignore
431      args.leftNs} and ${args.rightNs} 
432    `, {});
433  }
434
435  /**
436   *
437   * @param selectionParam
438   * @param sql 从饼图进程或者线程层点击进入Perf Profile时传入
439   */
440  private getCurrentDataFromDb(selectionParam: SelectionParam, sql?: string): void {
441    let filterSql = this.setFilterSql(selectionParam, sql);
442    this.queryData(
443      this.currentEventId,
444      'perf-queryCallchainsGroupSample',
445      `select p.callchain_id as sampleId,
446          p.thread_state as threadState,
447          p.thread_id    as tid,
448          p.count as count,
449          p.process_id   as pid,
450          p.event_count  as eventCount,
451          p.ts as ts,
452          p.event_type_id as eventTypeId
453      from (select callchain_id, s.thread_id, s.event_type_id, thread_state, process_id, 
454                count(callchain_id) as count,SUM(event_count) as event_count,
455                group_concat(s.timestamp_trace - t.start_ts,',') as ts
456            from perf_sample s, trace_range t
457            left join perf_thread thread on s.thread_id = thread.thread_id
458            where timestamp_trace between ${selectionParam.leftNs} + t.start_ts
459            and ${selectionParam.rightNs} + t.start_ts
460            and callchain_id != -1
461            and s.thread_id != 0 ${filterSql}
462            group by callchain_id, s.thread_id, thread_state, process_id) p`,
463      {
464        $startTime: selectionParam.leftNs,
465        $endTime: selectionParam.rightNs,
466        $sql: filterSql,
467      }
468    );
469  }
470  private setFilterSql(selectionParam: SelectionParam, sql?: string): string {
471    let filterSql = '';
472    if (sql) {
473      const cpus = selectionParam.perfAll ? [] : selectionParam.perfCpus;
474      const cpuFilter = cpus.length > 0 ? ` and s.cpu_id in (${cpus.join(',')}) ` : '';
475      let arg = `${sql}${cpuFilter}`.substring(3);
476      filterSql = `and ${arg}`;
477    } else {
478      const cpus = selectionParam.perfAll ? [] : selectionParam.perfCpus;
479      const processes = selectionParam.perfAll ? [] : selectionParam.perfProcess;
480      const threads = selectionParam.perfAll ? [] : selectionParam.perfThread;
481      if (cpus.length !== 0 || processes.length !== 0 || threads.length !== 0) {
482        const cpuFilter = cpus.length > 0 ? `or s.cpu_id in (${cpus.join(',')}) ` : '';
483        const processFilter = processes.length > 0 ? `or thread.process_id in (${processes.join(',')}) ` : '';
484        const threadFilter = threads.length > 0 ? `or s.thread_id in (${threads.join(',')})` : '';
485        let arg = `${cpuFilter}${processFilter}${threadFilter}`.substring(3);
486        filterSql = ` and (${arg})`;
487      }
488    }
489    let eventTypeId = selectionParam.perfEventTypeId;
490    const eventTypeFilter = eventTypeId !== undefined ? ` and s.event_type_id = ${eventTypeId}` : '';
491    filterSql += eventTypeFilter;
492    return filterSql;
493  }
494
495  clearAll(): void {
496    this.filesData = {};
497    this.samplesData = [];
498    this.threadData = {};
499    this.callChainData = {};
500    this.splitMapData = {};
501    this.currentTreeMapData = {};
502    this.currentTreeList = [];
503    this.searchValue = '';
504    this.dataSource = [];
505    this.allProcess = [];
506    this.dataCache.clearPerf();
507  }
508
509  initPerfCallChainTopDown(callChains: PerfCallChain[]): void {
510    this.callChainData = {};
511    callChains.forEach((callChain: PerfCallChain, index: number): void => {
512      this.setPerfCallChainFrameName(callChain);
513      this.addPerfGroupData(callChain);
514      let callChainDatum = this.callChainData[callChain.sampleId];
515      if (callChainDatum.length > 1) {
516        PerfCallChain.setNextNode(callChainDatum[callChainDatum.length - 2], callChainDatum[callChainDatum.length - 1]);
517      }
518    });
519  }
520
521  setPerfCallChainFrameName(callChain: PerfCallChain): void {
522    //设置调用栈的名称
523    callChain.canCharge = true;
524    if (this.filesData[callChain.fileId] && this.filesData[callChain.fileId].length > 0) {
525      callChain.fileName = this.filesData[callChain.fileId][0].fileName;
526      callChain.path = this.filesData[callChain.fileId][0].path;
527    } else {
528      callChain.fileName = 'unknown';
529    }
530  }
531
532  addPerfGroupData(callChain: PerfCallChain): void {
533    const currentCallChain = this.callChainData[callChain.sampleId] || [];
534    this.callChainData[callChain.sampleId] = currentCallChain;
535    if (currentCallChain.length > maxDepth) {
536      currentCallChain.splice(0, 1);
537    }
538    currentCallChain.push(callChain);
539  }
540
541  addOtherCallchainsData(countSample: PerfCountSample, list: PerfCallChain[]): void {
542    let threadCallChain = new PerfCallChain(); //新增的线程数据
543    threadCallChain.tid = countSample.tid;
544    threadCallChain.canCharge = false;
545    threadCallChain.isThread = true;
546    threadCallChain.name = `${this.threadData[countSample.tid].threadName || 'Thread'}(${countSample.tid})`;
547    let threadStateCallChain = new PerfCallChain(); //新增的线程状态数据
548    threadStateCallChain.tid = countSample.tid;
549    threadStateCallChain.isThreadState = true;
550    threadStateCallChain.name = countSample.threadState || 'Unknown State';
551    threadStateCallChain.fileName = threadStateCallChain.name === '-' ? 'Unknown Thread State' : '';
552    threadStateCallChain.canCharge = false;
553    if (!this.isHideThreadState) {
554      list.unshift(threadStateCallChain);
555    }
556    if (!this.isHideThread) {
557      list.unshift(threadCallChain);
558    }
559
560    if (this.isOnlyKernel) {
561      const flag = '[kernel.kallsyms]';
562      const newList = list.filter(i => i.fileName === flag || i.path === flag);
563      list.splice(0);
564      list.push(...newList);
565    }
566  }
567
568  private freshPerfCallchains(perfCountSamples: PerfCountSample[], isTopDown: boolean): void {
569    this.currentTreeMapData = {};
570    this.currentTreeList = [];
571    let totalSamplesCount = 0;
572    let totalEventCount = 0;
573    perfCountSamples.forEach((perfSample): void => {
574      totalSamplesCount += perfSample.count;
575      totalEventCount += perfSample.eventCount;
576      if (this.callChainData[perfSample.sampleId] && this.callChainData[perfSample.sampleId].length > 0) {
577        let perfCallChains = [...this.callChainData[perfSample.sampleId]];
578        this.addOtherCallchainsData(perfSample, perfCallChains);
579        let topIndex = isTopDown ? 0 : perfCallChains.length - 1;
580        if (perfCallChains.length > 0) {
581          let symbolName = '';
582          if (typeof perfCallChains[topIndex].name === 'number') {
583            //@ts-ignore
584            symbolName = this.dataCache.dataDict.get(perfCallChains[topIndex].name) || '';
585          } else {
586            //@ts-ignore
587            symbolName = perfCallChains[topIndex].name;
588          }
589          let perfRootNode = this.currentTreeMapData[symbolName + perfSample.pid];
590          if (perfRootNode === undefined) {
591            perfRootNode = new PerfCallChainMerageData();
592            this.currentTreeMapData[symbolName + perfSample.pid] = perfRootNode;
593            this.currentTreeList.push(perfRootNode);
594          }
595          PerfCallChainMerageData.merageCallChainSample(perfRootNode, perfCallChains[topIndex], perfSample, false);
596          this.mergeChildrenByIndex(perfRootNode, perfCallChains, topIndex, perfSample, isTopDown);
597        }
598      }
599    });
600    let rootMerageMap = this.mergeNodeData(totalEventCount, totalSamplesCount);
601    this.handleCurrentTreeList(totalEventCount, totalSamplesCount);
602    this.allProcess = Object.values(rootMerageMap);
603  }
604  private mergeNodeData(totalEventCount: number, totalSamplesCount: number): MergeMap {
605    let rootMerageMap: MergeMap = {};
606    // @ts-ignore
607    Object.values(this.currentTreeMapData).forEach((merageData: PerfCallChainMerageData): void => {
608      if (rootMerageMap[merageData.pid] === undefined) {
609        let perfProcessMerageData = new PerfCallChainMerageData(); //新增进程的节点数据
610        perfProcessMerageData.canCharge = false;
611        perfProcessMerageData.symbolName =
612          (this.threadData[merageData.tid].processName || 'Process') + `(${merageData.pid})`;
613        perfProcessMerageData.isProcess = true;
614        perfProcessMerageData.symbol = perfProcessMerageData.symbolName;
615        perfProcessMerageData.tid = merageData.tid;
616        perfProcessMerageData.children.push(merageData);
617        perfProcessMerageData.initChildren.push(merageData);
618        perfProcessMerageData.dur = merageData.dur;
619        perfProcessMerageData.count = merageData.dur;
620        perfProcessMerageData.eventCount = merageData.eventCount;
621        perfProcessMerageData.total = totalSamplesCount;
622        perfProcessMerageData.totalEvent = totalEventCount;
623        perfProcessMerageData.tsArray = [...merageData.tsArray];
624        rootMerageMap[merageData.pid] = perfProcessMerageData;
625      } else {
626        rootMerageMap[merageData.pid].children.push(merageData);
627        rootMerageMap[merageData.pid].initChildren.push(merageData);
628        rootMerageMap[merageData.pid].dur += merageData.dur;
629        rootMerageMap[merageData.pid].count += merageData.dur;
630        rootMerageMap[merageData.pid].eventCount += merageData.eventCount;
631        rootMerageMap[merageData.pid].total = totalSamplesCount;
632        rootMerageMap[merageData.pid].totalEvent = totalEventCount;
633        for (const ts of merageData.tsArray) {
634          rootMerageMap[merageData.pid].tsArray.push(ts);
635        }
636      }
637      merageData.parentNode = rootMerageMap[merageData.pid]; //子节点添加父节点的引用
638    });
639    return rootMerageMap;
640  }
641  private handleCurrentTreeList(totalEventCount: number, totalSamplesCount: number): void {
642    let id = 0;
643    this.currentTreeList.forEach((perfTreeNode: PerfCallChainMerageData): void => {
644      perfTreeNode.total = totalSamplesCount;
645      perfTreeNode.totalEvent = totalEventCount;
646      if (perfTreeNode.id === '') {
647        perfTreeNode.id = id + '';
648        id++;
649      }
650      if (perfTreeNode.parentNode) {
651        if (perfTreeNode.parentNode.id === '') {
652          perfTreeNode.parentNode.id = id + '';
653          id++;
654        }
655        perfTreeNode.parentId = perfTreeNode.parentNode.id;
656      }
657    });
658  }
659
660  mergeChildrenByIndex(
661    currentNode: PerfCallChainMerageData,
662    callChainDataList: PerfCallChain[],
663    index: number,
664    sample: PerfCountSample,
665    isTopDown: boolean
666  ): void {
667    if ((isTopDown && index >= callChainDataList.length - 1) || (!isTopDown && index <= 0)) {
668      return;
669    }
670    isTopDown ? index++ : index--;
671    let isEnd = isTopDown ? callChainDataList.length === index + 1 : index === 0;
672    let node: PerfCallChainMerageData;
673    if (
674      currentNode.initChildren.filter((child: PerfCallChainMerageData): boolean => {
675        let name: number | string | undefined = callChainDataList[index].name;
676        if (typeof name === 'number') {
677          name = this.dataCache.dataDict.get(name);
678        }
679        if (child.symbolName === name) {
680          node = child;
681          PerfCallChainMerageData.merageCallChainSample(child, callChainDataList[index], sample, isEnd);
682          return true;
683        }
684        return false;
685      }).length === 0
686    ) {
687      node = new PerfCallChainMerageData();
688      PerfCallChainMerageData.merageCallChainSample(node, callChainDataList[index], sample, isEnd);
689      currentNode.children.push(node);
690      currentNode.initChildren.push(node);
691      this.currentTreeList.push(node);
692      node.parentNode = currentNode;
693    }
694    if (node! && !isEnd) {
695      this.mergeChildrenByIndex(node, callChainDataList, index, sample, isTopDown);
696    }
697  }
698
699  //所有的操作都是针对整个树结构的 不区分特定的数据
700  splitPerfTree(samples: PerfCallChainMerageData[], name: string, isCharge: boolean, isSymbol: boolean): void {
701    samples.forEach((process: PerfCallChainMerageData): void => {
702      process.children = [];
703      if (isCharge) {
704        this.recursionPerfChargeInitTree(process, name, isSymbol);
705      } else {
706        this.recursionPerfPruneInitTree(process, name, isSymbol);
707      }
708    });
709    this.resetAllNode(samples);
710  }
711
712  recursionPerfChargeInitTree(sample: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void {
713    if ((isSymbol && sample.symbolName === symbolName) || (!isSymbol && sample.libName === symbolName)) {
714      (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(sample);
715      sample.isStore++;
716    }
717    if (sample.initChildren.length > 0) {
718      sample.initChildren.forEach((child: PerfCallChainMerageData): void => {
719        this.recursionPerfChargeInitTree(child, symbolName, isSymbol);
720      });
721    }
722  }
723
724  recursionPerfPruneInitTree(node: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void {
725    if ((isSymbol && node.symbolName === symbolName) || (!isSymbol && node.libName === symbolName)) {
726      (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(node);
727      node.isStore++;
728      this.pruneChildren(node, symbolName);
729    } else if (node.initChildren.length > 0) {
730      node.initChildren.forEach((child): void => {
731        this.recursionPerfPruneInitTree(child, symbolName, isSymbol);
732      });
733    }
734  }
735
736  //symbol lib prune
737  recursionPruneTree(sample: PerfCallChainMerageData, symbolName: string, isSymbol: boolean): void {
738    if ((isSymbol && sample.symbolName === symbolName) || (!isSymbol && sample.libName === symbolName)) {
739      sample.parent && sample.parent.children.splice(sample.parent.children.indexOf(sample), 1);
740    } else {
741      sample.children.forEach((child: PerfCallChainMerageData): void => {
742        this.recursionPruneTree(child, symbolName, isSymbol);
743      });
744    }
745  }
746
747  recursionChargeByRule(
748    sample: PerfCallChainMerageData,
749    ruleName: string,
750    rule: (node: PerfCallChainMerageData) => boolean
751  ): void {
752    if (sample.initChildren.length > 0) {
753      sample.initChildren.forEach((child): void => {
754        if (rule(child) && !child.isThread && !child.isState) {
755          (this.splitMapData[ruleName] = this.splitMapData[ruleName] || []).push(child);
756          child.isStore++;
757        }
758        this.recursionChargeByRule(child, ruleName, rule);
759      });
760    }
761  }
762
763  pruneChildren(sample: PerfCallChainMerageData, symbolName: string): void {
764    if (sample.initChildren.length > 0) {
765      sample.initChildren.forEach((child: PerfCallChainMerageData): void => {
766        child.isStore++;
767        (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(child);
768        this.pruneChildren(child, symbolName);
769      });
770    }
771  }
772
773  hideSystemLibrary(): void {
774    this.allProcess.forEach((item: PerfCallChainMerageData): void => {
775      item.children = [];
776      this.recursionChargeByRule(item, systemRuleName, (node: PerfCallChainMerageData): boolean => {
777        return node.path.startsWith(systemRuleName);
778      });
779    });
780  }
781
782  onlyKernel(): void {
783    this.allProcess.forEach((item: PerfCallChainMerageData): void => {
784      item.children = [];
785
786      function recursionHideChildren(
787        sample: PerfCallChainMerageData,
788        rule: (node: PerfCallChainMerageData) => boolean
789      ): void {
790        if (sample.initChildren.length > 0) {
791          sample.initChildren.forEach((child): void => {
792            if (rule(child)) {
793              child.isStore++;
794            }
795            recursionHideChildren(child, rule);
796          });
797        }
798      }
799      recursionHideChildren(item, (node: PerfCallChainMerageData): boolean => {
800        return node.libName !== '[kernel.kallsyms]';
801      });
802    });
803  }
804
805  hideNumMaxAndMin(startNum: number, endNum: string): void {
806    let max = endNum === '∞' ? Number.POSITIVE_INFINITY : parseInt(endNum);
807    this.allProcess.forEach((item: PerfCallChainMerageData): void => {
808      item.children = [];
809      this.recursionChargeByRule(item, numRuleName, (node: PerfCallChainMerageData): boolean => {
810        return node.dur < startNum || node.dur > max;
811      });
812    });
813  }
814
815  clearSplitMapData(symbolName: string): void {
816    if (symbolName in this.splitMapData) {
817      Reflect.deleteProperty(this.splitMapData, symbolName);
818    }
819  }
820
821  resetAllSymbol(symbols: string[]): void {
822    symbols.forEach((symbol: string): void => {
823      let list = this.splitMapData[symbol];
824      if (list !== undefined) {
825        list.forEach((item: PerfCallChainMerageData): void => {
826          item.isStore--;
827        });
828      }
829    });
830  }
831
832  resetAllNode(sample: PerfCallChainMerageData[]): void {
833    this.clearSearchNode();
834    sample.forEach((process: PerfCallChainMerageData): void => {
835      process.searchShow = true;
836      process.isSearch = false;
837    });
838    this.resetNewAllNode(sample);
839    if (this.searchValue !== '') {
840      this.findSearchNode(sample, this.searchValue, false);
841      this.resetNewAllNode(sample);
842    }
843  }
844
845  resetNewAllNode(sampleArray: PerfCallChainMerageData[]): void {
846    sampleArray.forEach((process: PerfCallChainMerageData): void => {
847      process.children = [];
848    });
849    let values = this.currentTreeList.map((item: PerfCallChainMerageData): PerfCallChainMerageData => {
850      item.children = [];
851      return item;
852    });
853    values.forEach((sample: PerfCallChainMerageData): void => {
854      if (sample.parentNode !== undefined) {
855        if (sample.isStore === 0 && sample.searchShow) {
856          let parentNode = sample.parentNode;
857          while (parentNode !== undefined && !(parentNode.isStore === 0 && parentNode.searchShow)) {
858            parentNode = parentNode.parentNode!;
859          }
860          if (parentNode) {
861            sample.parent = parentNode;
862            parentNode.children.push(sample);
863          }
864        }
865      }
866    });
867  }
868
869  kernelCombination(): void {
870    function mergeChildren(item: PerfCallChainMerageData): void {
871      if (item.children.length <= 0) {
872        return;
873      }
874      item.children = item.children.reduce((total: PerfCallChainMerageData[], pfcall: PerfCallChainMerageData): PerfCallChainMerageData[] => {
875        for (const prev of total) {
876          if (pfcall.symbol === prev.symbol) {
877            prev.children.push(...pfcall.children);
878            prev.total += pfcall.total;
879            prev.count += pfcall.count;
880            prev.totalEvent += pfcall.totalEvent;
881            prev.eventCount += pfcall.eventCount;
882            return total;
883          }
884        }
885        total.push(pfcall);
886        return total;
887      }, [] as PerfCallChainMerageData[]);
888      for (const child of item.children) {
889        mergeChildren(child);
890      }
891    }
892    this.allProcess.forEach((item: PerfCallChainMerageData): void => {
893      mergeChildren(item);
894    });
895  }
896
897  findSearchNode(sampleArray: PerfCallChainMerageData[], search: string, parentSearch: boolean): void {
898    search = search.toLocaleLowerCase();
899    sampleArray.forEach((sample: PerfCallChainMerageData): void => {
900      if ((sample.symbol && sample.symbol.toLocaleLowerCase().includes(search)) || parentSearch) {
901        sample.searchShow = true;
902        sample.isSearch = sample.symbol !== undefined && sample.symbol.toLocaleLowerCase().includes(search);
903        let parentNode = sample.parent;
904        while (parentNode !== undefined && !parentNode.searchShow) {
905          parentNode.searchShow = true;
906          parentNode = parentNode.parent;
907        }
908      } else {
909        sample.searchShow = false;
910        sample.isSearch = false;
911      }
912      if (sample.children.length > 0) {
913        this.findSearchNode(sample.children, search, sample.searchShow);
914      }
915    });
916  }
917
918  clearSearchNode(): void {
919    this.currentTreeList.forEach((sample: PerfCallChainMerageData): void => {
920      sample.searchShow = true;
921      sample.isSearch = false;
922    });
923  }
924
925  splitAllProcess(processArray: { select: string; name: string; type: string; checked: boolean }[]): void {
926    processArray.forEach((item: { select: string; name: string; type: string; checked: boolean }): void => {
927      this.allProcess.forEach((process): void => {
928        if (item.select === '0') {
929          this.recursionPerfChargeInitTree(process, item.name, item.type === 'symbol');
930        } else {
931          this.recursionPerfPruneInitTree(process, item.name, item.type === 'symbol');
932        }
933      });
934      if (!item.checked) {
935        this.resetAllSymbol([item.name]);
936      }
937    });
938  }
939  resolvingAction(params: unknown[]): unknown[] {
940    if (params.length > 0) {
941      for (let item of params) {
942        //@ts-ignore
943        if (item.funcName && item.funcArgs) {
944          //@ts-ignore
945          let result = this.handleDataByFuncName(item.funcName, item.funcArgs);
946          if (result) {
947            //@ts-ignore
948            return result;
949          }
950        }
951      }
952      this.dataSource = this.allProcess.filter((process: PerfCallChainMerageData): boolean => {
953        return process.children && process.children.length > 0;
954      });
955    }
956    return this.dataSource;
957  }
958  private queryDataFromDb(funcArgs: unknown[]): void {
959    if (funcArgs[1]) {
960      let sql = '';
961      //@ts-ignore
962      if (funcArgs[1].processId !== undefined) {
963        //@ts-ignore
964        sql += `and thread.process_id = ${funcArgs[1].processId}`;
965      }
966      //@ts-ignore
967      if (funcArgs[1].threadId !== undefined) {
968        //@ts-ignore
969        sql += ` and s.thread_id = ${funcArgs[1].threadId}`;
970      }
971      //@ts-ignore
972      this.getCurrentDataFromDb(funcArgs[0], sql);
973    } else {
974      //@ts-ignore
975      this.getCurrentDataFromDb(funcArgs[0]);
976    }
977  }
978  private handleDataByFuncName(funcName: string, funcArgs: unknown[]): unknown {
979    let result;
980    switch (funcName) {
981      case 'getCallChainsBySampleIds':
982        this.freshPerfCallchains(this.samplesData, funcArgs[0] as boolean);
983        break;
984      case 'getCurrentDataFromDb':
985        this.queryDataFromDb(funcArgs);
986        break;
987      case 'hideSystemLibrary':
988        this.hideSystemLibrary();
989        break;
990      case 'hideThread':
991        this.isHideThread = funcArgs[0] as boolean;
992        break;
993      case 'hideThreadState':
994        this.isHideThreadState = funcArgs[0] as boolean;
995        break;
996      case 'onlyKernel':
997        this.isOnlyKernel = funcArgs[0] as boolean;
998        break;
999      case 'hideNumMaxAndMin':
1000        this.hideNumMaxAndMin(funcArgs[0] as number, funcArgs[1] as string);
1001        break;
1002      case 'splitAllProcess':
1003        //@ts-ignore
1004        this.splitAllProcess(funcArgs[0]);
1005        break;
1006      case 'resetAllNode':
1007        this.resetAllNode(this.allProcess);
1008        break;
1009      case 'resotreAllNode':
1010        this.resetAllSymbol(funcArgs[0] as string[]);
1011        break;
1012      case 'clearSplitMapData':
1013        this.clearSplitMapData(funcArgs[0] as string);
1014        break;
1015      case 'splitTree':
1016        this.splitPerfTree(this.allProcess, funcArgs[0] as string, funcArgs[1] as boolean, funcArgs[2] as boolean);
1017        break;
1018      case 'setSearchValue':
1019        this.searchValue = funcArgs[0] as string;
1020        break;
1021      case 'setCombineCallChain':
1022        this.isAnalysis = true;
1023        break;
1024      case 'setPerfBottomUp':
1025        this.isPerfBottomUp = true;
1026        break;
1027      case 'combineAnalysisCallChain':
1028        result = this.combineCallChainForAnalysis();
1029        break;
1030      case 'getBottomUp':
1031        result = this.getBottomUp();
1032        break;
1033      case 'kernelCombination':
1034        this.kernelCombination();
1035        break;
1036    }
1037    return result;
1038  }
1039
1040  combineCallChainForAnalysis(obj?: unknown): PerfAnalysisSample[] {
1041    let sampleCallChainList: Array<PerfAnalysisSample> = [];
1042    for (let sample of this.samplesData) {
1043      let callChains = [...this.callChainData[sample.sampleId]];
1044      const lastCallChain = callChains[callChains.length - 1];
1045      const threadName = this.threadData[sample.tid].threadName || 'Thread';
1046      const processName = this.threadData[sample.pid].threadName || 'Process';
1047      const funcName = this.dataCache.dataDict.get(lastCallChain.name as number);
1048      if (
1049        //@ts-ignore
1050        (obj && obj.libId === lastCallChain.fileId && obj.libName === lastCallChain.fileName) ||
1051        //@ts-ignore
1052        (obj && obj.symbolId === lastCallChain.symbolId && obj.symbolName === funcName) ||
1053        !obj
1054      ) {
1055        let analysisSample = new PerfAnalysisSample(
1056          threadName,
1057          processName,
1058          lastCallChain.fileId,
1059          lastCallChain.fileName,
1060          lastCallChain.symbolId,
1061          this.dataCache.dataDict.get(lastCallChain.name as number) || ''
1062        );
1063        analysisSample.tid = sample.tid;
1064        analysisSample.pid = sample.pid;
1065        analysisSample.count = sample.count;
1066        analysisSample.threadState = sample.threadState;
1067        analysisSample.eventCount = sample.eventCount;
1068        analysisSample.sampleId = sample.sampleId;
1069        sampleCallChainList.push(analysisSample);
1070      }
1071    }
1072    return sampleCallChainList;
1073  }
1074
1075  getBottomUp(): PerfBottomUpStruct[] {
1076    const topUp = new PerfBottomUpStruct('topUp');
1077    let perfTime = 1;
1078    for (let sample of this.samplesData) {
1079      let currentNode = topUp;
1080      let callChains = this.callChainData[sample.sampleId];
1081      for (let i = 0; i < callChains.length; i++) {
1082        if (i === 0) {
1083          currentNode = topUp;
1084        }
1085        let item = callChains[i];
1086        const existingNode = currentNode.children.find(
1087          (child) => child.symbolName === `${item.name}(${item.fileName})`
1088        );
1089        if (existingNode) {
1090          existingNode.tsArray.push(...sample.ts.split(',').map(Number));
1091          currentNode = existingNode;
1092          existingNode.totalTime += perfTime * sample.count;
1093          existingNode.eventCount += sample.eventCount;
1094          existingNode.calculateSelfTime();
1095          existingNode.notifyParentUpdateSelfTime();
1096        } else {
1097          const symbolName = this.dataCache.dataDict.get(item.name as number) || '';
1098          let newNode = new PerfBottomUpStruct(`${symbolName}(${item.fileName})`);
1099          newNode.totalTime = perfTime * sample.count;
1100          newNode.eventCount = sample.eventCount;
1101          newNode.tsArray = sample.ts.split(',').map(Number);
1102          currentNode.addChildren(newNode);
1103          newNode.calculateSelfTime();
1104          newNode.notifyParentUpdateSelfTime();
1105          currentNode = newNode;
1106        }
1107      }
1108    }
1109    topUp.children.forEach((child: PerfBottomUpStruct): void => {
1110      child.parentNode = undefined;
1111    });
1112
1113    let date = this.topUpDataToBottomUpData(topUp.children);
1114    if (this.isPerfBottomUp) {
1115      this.isPerfBottomUp = false;
1116    }
1117    return date;
1118  }
1119
1120  private topUpDataToBottomUpData(perfPositiveArray: Array<PerfBottomUpStruct>): Array<PerfBottomUpStruct> {
1121    let reverseTreeArray: Array<PerfBottomUpStruct> = [];
1122    const recursionTree = (perfBottomUpStruct: PerfBottomUpStruct): void => {
1123      if (perfBottomUpStruct.selfTime > 0) {
1124        const clonePerfBottomUpStruct = new PerfBottomUpStruct(perfBottomUpStruct.symbolName);
1125        clonePerfBottomUpStruct.selfTime = perfBottomUpStruct.selfTime;
1126        clonePerfBottomUpStruct.totalTime = perfBottomUpStruct.totalTime;
1127        clonePerfBottomUpStruct.eventCount = perfBottomUpStruct.eventCount;
1128        clonePerfBottomUpStruct.tsArray = [...perfBottomUpStruct.tsArray];
1129        reverseTreeArray.push(clonePerfBottomUpStruct);
1130        this.copyParentNode(clonePerfBottomUpStruct, perfBottomUpStruct);
1131      }
1132      if (perfBottomUpStruct.children.length > 0) {
1133        for (const children of perfBottomUpStruct.children) {
1134          children.parentNode = perfBottomUpStruct;
1135          recursionTree(children);
1136        }
1137      }
1138    };
1139    for (const perfBottomUpStruct of perfPositiveArray) {
1140      recursionTree(perfBottomUpStruct);
1141    }
1142    return this.mergeTreeBifurcation(reverseTreeArray, null);
1143  }
1144
1145  private mergeTreeBifurcation(
1146    reverseTreeArray: Array<PerfBottomUpStruct> | null,
1147    parent: PerfBottomUpStruct | null
1148  ): Array<PerfBottomUpStruct> {
1149    const sameSymbolMap = new Map<string, PerfBottomUpStruct>();
1150    const currentLevelData: Array<PerfBottomUpStruct> = [];
1151    const dataArray = reverseTreeArray || parent?.frameChildren;
1152    if (!dataArray) {
1153      return [];
1154    }
1155    for (const perfBottomUpStruct of dataArray) {
1156      let symbolKey = perfBottomUpStruct.symbolName;
1157      let bottomUpStruct: PerfBottomUpStruct;
1158      if (sameSymbolMap.has(symbolKey)) {
1159        bottomUpStruct = sameSymbolMap.get(symbolKey)!;
1160        bottomUpStruct.totalTime += perfBottomUpStruct.totalTime;
1161        bottomUpStruct.selfTime += perfBottomUpStruct.selfTime;
1162        for (const ts of perfBottomUpStruct.tsArray) {
1163          bottomUpStruct.tsArray.push(ts);
1164        }
1165      } else {
1166        bottomUpStruct = perfBottomUpStruct;
1167        sameSymbolMap.set(symbolKey, bottomUpStruct);
1168        currentLevelData.push(bottomUpStruct);
1169        if (parent) {
1170          parent.addChildren(bottomUpStruct);
1171        }
1172      }
1173      bottomUpStruct.frameChildren?.push(...perfBottomUpStruct.children);
1174    }
1175
1176    for (const data of currentLevelData) {
1177      this.mergeTreeBifurcation(null, data);
1178      data.frameChildren = [];
1179    }
1180    if (reverseTreeArray) {
1181      return currentLevelData;
1182    } else {
1183      return [];
1184    }
1185  }
1186
1187  /**
1188   * copy整体调用链,从栈顶函数一直copy到栈底函数,
1189   * 给Parent设置selfTime,totalTime设置为children的selfTime,totalTime
1190   *  */
1191  private copyParentNode(perfBottomUpStruct: PerfBottomUpStruct, bottomUpStruct: PerfBottomUpStruct): void {
1192    if (bottomUpStruct.parentNode) {
1193      const copyParent = new PerfBottomUpStruct(bottomUpStruct.parentNode.symbolName);
1194      copyParent.selfTime = perfBottomUpStruct.selfTime;
1195      copyParent.totalTime = perfBottomUpStruct.totalTime;
1196      copyParent.eventCount = perfBottomUpStruct.eventCount;
1197      copyParent.tsArray = [...perfBottomUpStruct.tsArray];
1198      perfBottomUpStruct.addChildren(copyParent);
1199      this.copyParentNode(copyParent, bottomUpStruct.parentNode);
1200    }
1201  }
1202}
1203
1204export class PerfFile {
1205  fileId: number = 0;
1206  symbol: string = '';
1207  path: string = '';
1208  fileName: string = '';
1209
1210  static setFileName(data: PerfFile): void {
1211    if (data.path) {
1212      let number = data.path.lastIndexOf('/');
1213      if (number > 0) {
1214        data.fileName = data.path.substring(number + 1);
1215        return;
1216      }
1217    }
1218    data.fileName = data.path;
1219  }
1220
1221  setFileName(): void {
1222    if (this.path) {
1223      let number = this.path.lastIndexOf('/');
1224      if (number > 0) {
1225        this.fileName = this.path.substring(number + 1);
1226        return;
1227      }
1228    }
1229    this.fileName = this.path;
1230  }
1231}
1232
1233export class PerfThread {
1234  tid: number = 0;
1235  pid: number = 0;
1236  threadName: string = '';
1237  processName: string = '';
1238}
1239
1240export class PerfCallChain {
1241  startNS: number = 0;
1242  dur: number = 0;
1243  sampleId: number = 0;
1244  callChainId: number = 0;
1245  vaddrInFile: number = 0;
1246  tid: number = 0;
1247  pid: number = 0;
1248  name: number | string = 0;
1249  fileName: string = '';
1250  threadState: string = '';
1251  fileId: number = 0;
1252  symbolId: number = 0;
1253  path: string = '';
1254  count: number = 0;
1255  eventCount: number = 0;
1256  parentId: string = ''; //合并之后区分的id
1257  id: string = '';
1258  topDownMerageId: string = ''; //top down合并使用的id
1259  topDownMerageParentId: string = ''; //top down合并使用的id
1260  bottomUpMerageId: string = ''; //bottom up合并使用的id
1261  bottomUpMerageParentId: string = ''; //bottom up合并使用的id
1262  depth: number = 0;
1263  canCharge: boolean = true;
1264  previousNode: PerfCallChain | undefined = undefined; //将list转换为一个链表结构
1265  nextNode: PerfCallChain | undefined = undefined;
1266  isThread: boolean = false;
1267  isProcess: boolean = false;
1268  isThreadState: boolean = false;
1269
1270  static setNextNode(currentNode: PerfCallChain, nextNode: PerfCallChain): void {
1271    currentNode.nextNode = nextNode;
1272    nextNode.previousNode = currentNode;
1273  }
1274
1275  static setPreviousNode(currentNode: PerfCallChain, prevNode: PerfCallChain): void {
1276    currentNode.previousNode = prevNode;
1277    prevNode.nextNode = currentNode;
1278  }
1279
1280  static merageCallChain(currentNode: PerfCallChain, callChain: PerfCallChain): void {
1281    currentNode.startNS = callChain.startNS;
1282    currentNode.tid = callChain.tid;
1283    currentNode.pid = callChain.pid;
1284    currentNode.sampleId = callChain.sampleId;
1285    currentNode.dur = callChain.dur;
1286    currentNode.count = callChain.count;
1287    currentNode.eventCount = callChain.eventCount;
1288  }
1289}
1290
1291export class PerfCallChainMerageData extends ChartStruct {
1292  // @ts-ignore
1293  #parentNode: PerfCallChainMerageData | undefined = undefined;
1294  // @ts-ignore
1295  #total = 0;
1296  // @ts-ignore
1297  #totalEvent = 0;
1298  id: string = '';
1299  parentId: string = '';
1300  parent: PerfCallChainMerageData | undefined = undefined;
1301  symbolName: string = '';
1302  symbol: string = '';
1303  libName: string = '';
1304  path: string = '';
1305  weight: string = '';
1306  weightPercent: string = '';
1307  selfDur: number = 0;
1308  dur: number = 0;
1309  tid: number = 0;
1310  pid: number = 0;
1311  isStore = 0;
1312  canCharge: boolean = true;
1313  children: PerfCallChainMerageData[] = [];
1314  initChildren: PerfCallChainMerageData[] = [];
1315  type: number = 0;
1316  vaddrInFile: number = 0;
1317  isSelected: boolean = false;
1318  searchShow: boolean = true;
1319  isSearch: boolean = false;
1320  isState: boolean = false;
1321  set parentNode(data: PerfCallChainMerageData | undefined) {
1322    this.parent = data;
1323    this.#parentNode = data;
1324  }
1325
1326  get parentNode(): PerfCallChainMerageData | undefined {
1327    return this.#parentNode;
1328  }
1329
1330  set total(data: number) {
1331    this.#total = data;
1332    this.weight = `${this.dur}`;
1333    this.weightPercent = `${((this.dur / data) * 100).toFixed(1)}%`;
1334  }
1335
1336  get total(): number {
1337    return this.#total;
1338  }
1339
1340  set totalEvent(data: number) {
1341    this.#totalEvent = data;
1342    this.eventPercent = `${((this.eventCount / data) * 100).toFixed(1)}%`;
1343  }
1344
1345  get totalEvent(): number {
1346    return this.#totalEvent;
1347  }
1348
1349  static merageCallChainSample(
1350    currentNode: PerfCallChainMerageData,
1351    callChain: PerfCallChain,
1352    sample: PerfCountSample,
1353    isEnd: boolean
1354  ): void {
1355    if (currentNode.symbolName === '') {
1356      let symbolName = '';
1357      if (typeof callChain.name === 'number') {
1358        symbolName = DataCache.getInstance().dataDict.get(callChain.name) || '';
1359      } else {
1360        symbolName = callChain.name;
1361      }
1362      currentNode.symbol = `${symbolName} ${callChain.fileName ? `(${callChain.fileName})` : ''}`;
1363      currentNode.symbolName = symbolName;
1364      currentNode.pid = sample.pid;
1365      currentNode.tid = sample.tid;
1366      currentNode.libName = callChain.fileName;
1367      currentNode.vaddrInFile = callChain.vaddrInFile;
1368      currentNode.lib = callChain.fileName;
1369      currentNode.addr = `${'0x'}${callChain.vaddrInFile.toString(16)}`;
1370      currentNode.canCharge = callChain.canCharge;
1371      if (callChain.path) {
1372        currentNode.path = callChain.path;
1373      }
1374    }
1375    if (isEnd) {
1376      currentNode.selfDur += sample.count;
1377    }
1378    if (callChain.isThread && !currentNode.isThread) {
1379      currentNode.isThread = callChain.isThread;
1380    }
1381    if (callChain.isThreadState && !currentNode.isState) {
1382      currentNode.isState = callChain.isThreadState;
1383    }
1384    currentNode.dur += sample.count;
1385    currentNode.count += sample.count;
1386    currentNode.eventCount += sample.eventCount;
1387    currentNode.tsArray.push(...sample.ts.split(',').map(Number));
1388  }
1389}
1390
1391export class PerfCountSample {
1392  sampleId: number = 0;
1393  tid: number = 0;
1394  count: number = 0;
1395  threadState: string = '';
1396  pid: number = 0;
1397  eventCount: number = 0;
1398  ts: string = '';
1399}
1400
1401export class PerfStack {
1402  symbol: string = '';
1403  path: string = '';
1404  fileId: number = 0;
1405  type: number = 0;
1406  vaddrInFile: number = 0;
1407}
1408
1409export class PerfCmdLine {
1410  report_value: string = '';
1411}
1412
1413class PerfAnalysisSample extends PerfCountSample {
1414  threadName: string;
1415  processName: string;
1416  libId: number;
1417  libName: string;
1418  symbolId: number;
1419  symbolName: string;
1420
1421  constructor(
1422    threadName: string,
1423    processName: string,
1424    libId: number,
1425    libName: string,
1426    symbolId: number,
1427    symbolName: string
1428  ) {
1429    super();
1430    this.threadName = threadName;
1431    this.processName = processName;
1432    this.libId = libId;
1433    this.libName = libName;
1434    this.symbolId = symbolId;
1435    this.symbolName = symbolName;
1436  }
1437}
1438
1439export function timeMsFormat2p(ns: number): string {
1440  let currentNs = ns;
1441  let hour1 = 3600_000;
1442  let minute1 = 60_000;
1443  let second1 = 1_000; // 1 second
1444  let perfResult = '';
1445  if (currentNs >= hour1) {
1446    perfResult += `${Math.floor(currentNs / hour1).toFixed(2)}h`;
1447    return perfResult;
1448  }
1449  if (currentNs >= minute1) {
1450    perfResult += `${Math.floor(currentNs / minute1).toFixed(2)}min`;
1451    return perfResult;
1452  }
1453  if (currentNs >= second1) {
1454    perfResult += `${Math.floor(currentNs / second1).toFixed(2)}s`;
1455    return perfResult;
1456  }
1457  if (currentNs > 0) {
1458    perfResult += `${currentNs.toFixed(2)}ms`;
1459    return perfResult;
1460  }
1461  if (perfResult === '') {
1462    perfResult = '0s';
1463  }
1464  return perfResult;
1465}
1466
1467class HiPrefSample {
1468  name: string = '';
1469  depth: number = 0;
1470  callchain_id: number = 0;
1471  totalTime: number = 0;
1472  thread_id: number = 0;
1473  id: number = 0;
1474  eventCount: number = 0;
1475  startTime: number = 0;
1476  endTime: number = 0;
1477  timeTip: number = 0;
1478  cpu_id: number = 0;
1479  stack?: Array<HiPerfSymbol>;
1480}
1481