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