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
16
17export class ChartStruct {
18  depth: number = 0;
19  symbol: string = '';
20  lib: string = '';
21  path: string = '';
22  addr: string = '';
23  size: number = 0;
24  count: number = 0;
25  eventCount: number = 0;
26  eventPercent: string = '';
27  dur: number = 0;
28  parent: ChartStruct | undefined;
29  children: Array<ChartStruct> = [];
30  isSearch: boolean = false;
31  tsArray: Array<number> = []; // 每个绘制的函数由哪些时间点的样本组成
32  countArray: Array<number> = []; // native hook统计模式下一个时间点有多次分配
33  durArray: Array<number> = [];
34  isThread: boolean = false;
35  isProcess: boolean = false;
36}
37
38export class Msg {
39  tag: string = '';
40  index: number = 0;
41  isSending: boolean = false;
42  data: Array<unknown> = [];
43}
44
45export class HiPerfSymbol {
46  id: number = 0;
47  startTime: number = 0;
48  eventCount: number = 0;
49  endTime: number = 0;
50  totalTime: number = 0;
51  fileId: number = 0;
52  symbolId: number = 0;
53  cpu_id: number = 0;
54  depth: number = 0;
55  children?: Array<HiPerfSymbol>;
56  callchain_id: number = 0;
57  thread_id: number = 0;
58  name: string = '';
59
60  public clone(): HiPerfSymbol {
61    const cloneSymbol = new HiPerfSymbol();
62    cloneSymbol.children = [];
63    cloneSymbol.depth = this.depth;
64    return cloneSymbol;
65  }
66}
67
68export class MerageBean extends ChartStruct {
69  #parentNode: MerageBean | undefined = undefined;
70  #total = 0;
71  parent: MerageBean | undefined = undefined;
72  id: string = '';
73  parentId: string = '';
74  self?: string = '0s';
75  weight?: string;
76  weightPercent?: string;
77  selfDur: number = 0;
78  dur: number = 0;
79  pid: number = 0;
80  canCharge: boolean = true;
81  isStore = 0;
82  isSelected: boolean = false;
83  searchShow: boolean = true;
84  children: MerageBean[] = [];
85  initChildren: MerageBean[] = [];
86  type: number = 0;
87  set parentNode(data: MerageBean | undefined) {
88    this.parent = data;
89    this.#parentNode = data;
90  }
91
92  get parentNode(): MerageBean | undefined {
93    return this.#parentNode;
94  }
95
96  set total(data: number) {
97    this.#total = data;
98    this.weight = `${getProbablyTime(this.dur)}`;
99    this.weightPercent = `${((this.dur / data) * 100).toFixed(1)}%`;
100  }
101
102  get total(): number {
103    return this.#total;
104  }
105}
106
107class MerageBeanDataSplit {
108  systmeRuleName = '/system/';
109  numRuleName = '/max/min/';
110
111  //所有的操作都是针对整个树结构的, 不区分特定的数据
112  splitTree(
113    splitMapData: unknown,
114    data: MerageBean[],
115    name: string,
116    isCharge: boolean,
117    isSymbol: boolean,
118    currentTreeList: ChartStruct[],
119    searchValue: string
120  ): void {
121    data.forEach((process) => {
122      process.children = [];
123      if (isCharge) {
124        this.recursionChargeInitTree(splitMapData, process, name, isSymbol);
125      } else {
126        this.recursionPruneInitTree(splitMapData, process, name, isSymbol);
127      }
128    });
129    this.resetAllNode(data, currentTreeList, searchValue);
130  }
131
132  recursionChargeInitTree(splitMapData: unknown, node: MerageBean, symbolName: string, isSymbol: boolean): void {
133    if ((isSymbol && node.symbol === symbolName) || (!isSymbol && node.lib === symbolName)) {
134      //@ts-ignore
135      (splitMapData[symbolName] = splitMapData[symbolName] || []).push(node);
136      node.isStore++;
137    }
138    if (node.initChildren.length > 0) {
139      node.initChildren.forEach((child) => {
140        this.recursionChargeInitTree(splitMapData, child, symbolName, isSymbol);
141      });
142    }
143  }
144
145  recursionPruneInitTree(splitMapData: unknown, node: MerageBean, symbolName: string, isSymbol: boolean): void {
146    if ((isSymbol && node.symbol === symbolName) || (!isSymbol && node.lib === symbolName)) {
147      //@ts-ignore
148      (splitMapData[symbolName] = splitMapData[symbolName] || []).push(node);
149      node.isStore++;
150      this.pruneChildren(splitMapData, node, symbolName);
151    } else if (node.initChildren.length > 0) {
152      node.initChildren.forEach((child) => {
153        this.recursionPruneInitTree(splitMapData, child, symbolName, isSymbol);
154      });
155    }
156  }
157
158  //symbol lib prune
159  recursionPruneTree(node: MerageBean, symbolName: string, isSymbol: boolean): void {
160    if ((isSymbol && node.symbol === symbolName) || (!isSymbol && node.lib === symbolName)) {
161      node.parent && node.parent.children.splice(node.parent.children.indexOf(node), 1);
162    } else {
163      node.children.forEach((child) => {
164        this.recursionPruneTree(child, symbolName, isSymbol);
165      });
166    }
167  }
168
169  recursionChargeByRule(
170    splitMapData: unknown,
171    node: MerageBean,
172    ruleName: string,
173    rule: (node: MerageBean) => boolean
174  ): void {
175    if (node.initChildren.length > 0) {
176      node.initChildren.forEach((child) => {
177        if (rule(child)) {
178          //@ts-ignore
179          (splitMapData[ruleName] = splitMapData[ruleName] || []).push(child);
180          child.isStore++;
181        }
182        this.recursionChargeByRule(splitMapData, child, ruleName, rule);
183      });
184    }
185  }
186
187  pruneChildren(splitMapData: unknown, node: MerageBean, symbolName: string): void {
188    if (node.initChildren.length > 0) {
189      node.initChildren.forEach((child) => {
190        child.isStore++;
191        //@ts-ignore
192        (splitMapData[symbolName] = splitMapData[symbolName] || []).push(child);
193        this.pruneChildren(splitMapData, child, symbolName);
194      });
195    }
196  }
197
198  hideSystemLibrary(allProcess: MerageBean[], splitMapData: unknown): void {
199    allProcess.forEach((item) => {
200      item.children = [];
201      this.recursionChargeByRule(splitMapData, item, this.systmeRuleName, (node) => {
202        return node.path.startsWith(this.systmeRuleName);
203      });
204    });
205  }
206
207  hideNumMaxAndMin(allProcess: MerageBean[], splitMapData: unknown, startNum: number, endNum: string): void {
208    let max = endNum === '∞' ? Number.POSITIVE_INFINITY : parseInt(endNum);
209    allProcess.forEach((item) => {
210      item.children = [];
211      this.recursionChargeByRule(splitMapData, item, this.numRuleName, (node) => {
212        return node.count < startNum || node.count > max;
213      });
214    });
215  }
216
217  resotreAllNode(splitMapData: unknown, symbols: string[]): void {
218    symbols.forEach((symbol) => {
219      //@ts-ignore
220      let list = splitMapData[symbol];
221      if (list !== undefined) {
222        list.forEach((item: unknown) => {
223          //@ts-ignore
224          item.isStore--;
225        });
226      }
227    });
228  }
229
230  resetAllNode(data: MerageBean[], currentTreeList: ChartStruct[], searchValue: string): void {
231    // 去除全部节点上次筛选的标记
232    this.clearSearchNode(currentTreeList);
233    // 去除线程上次筛选的标记
234    data.forEach((process) => {
235      process.searchShow = true;
236      process.isSearch = false;
237    });
238    // 恢复上次筛选
239    this.resetNewAllNode(data, currentTreeList);
240    if (searchValue !== '') {
241      // 将筛选匹配的节点做上标记,search = true,否则都是false
242      this.findSearchNode(data, searchValue, false);
243      // 将searchshow为true的节点整理树结构,其余的不管
244      this.resetNewAllNode(data, currentTreeList);
245    }
246  }
247
248  resetNewAllNode(data: MerageBean[], currentTreeList: ChartStruct[]): void {
249    data.forEach((process) => {
250      process.children = [];
251    });
252    // 所有节点的children都置空
253    let values = currentTreeList.map((item: ChartStruct) => {
254      item.children = [];
255      return item;
256    });
257    values.forEach((item: unknown) => {
258      //@ts-ignore
259      if (item.parentNode !== undefined) {
260        //@ts-ignore
261        if (item.isStore === 0 && item.searchShow) {
262          /*
263          拿到当前节点的父节点,如果它的父节点没有被搜索,则找到它父节点的父节点
264          */
265          //@ts-ignore
266          let parentNode = item.parentNode;
267          while (parentNode !== undefined && !(parentNode.isStore === 0 && parentNode.searchShow)) {
268            parentNode = parentNode.parentNode;
269          }
270          if (parentNode) {
271            //@ts-ignore
272            item.currentTreeParentNode = parentNode;
273            parentNode.children.push(item);
274          }
275        }
276      }
277    });
278  }
279
280  findSearchNode(data: MerageBean[], search: string, parentSearch: boolean): void {
281    search = search.toLocaleLowerCase();
282    data.forEach((item) => {
283      if ((item.symbol && item.symbol.toLocaleLowerCase().includes(search)) || parentSearch) {
284        item.searchShow = true;
285        item.isSearch = item.symbol !== undefined && item.symbol.toLocaleLowerCase().includes(search);
286        let parentNode = item.parent;
287        while (parentNode && !parentNode.searchShow) {
288          parentNode.searchShow = true;
289          parentNode = parentNode.parent;
290        }
291      } else {
292        item.searchShow = false;
293        item.isSearch = false;
294      }
295      if (item.children.length > 0) {
296        this.findSearchNode(item.children, search, item.searchShow);
297      }
298    });
299  }
300
301  clearSearchNode(currentTreeList: ChartStruct[]): void {
302    currentTreeList.forEach((node) => {
303      //@ts-ignore
304      node.searchShow = true;
305      node.isSearch = false;
306    });
307  }
308
309  splitAllProcess(allProcess: unknown[], splitMapData: unknown, list: unknown): void {
310    //@ts-ignore
311    list.forEach((item: unknown) => {
312      allProcess.forEach((process) => {
313        //@ts-ignore
314        if (item.select === '0') {
315          //@ts-ignore
316          this.recursionChargeInitTree(splitMapData, process, item.name, item.type === 'symbol');
317        } else {
318          //@ts-ignore
319          this.recursionPruneInitTree(splitMapData, process, item.name, item.type === 'symbol');
320        }
321      });
322      //@ts-ignore
323      if (!item.checked) {
324        //@ts-ignore
325        this.resotreAllNode(splitMapData, [item.name]);
326      }
327    });
328  }
329}
330
331export let merageBeanDataSplit = new MerageBeanDataSplit();
332
333export abstract class LogicHandler {
334  abstract handle(data: unknown): void;
335  queryData(eventId: string, queryName: string, sql: string, args: unknown): void {
336    self.postMessage({
337      id: eventId,
338      type: queryName,
339      isQuery: true,
340      args: args,
341      sql: sql,
342    });
343  }
344
345  abstract clearAll(): void;
346}
347
348let dec = new TextDecoder();
349
350export let setFileName = (path: string): string => {
351  let fileName = '';
352  if (path) {
353    let number = path.lastIndexOf('/');
354    if (number > 0) {
355      fileName = path.substring(number + 1);
356      return fileName;
357    }
358  }
359  return path;
360};
361
362let pagination = (page: number, pageSize: number, source: Array<unknown>): unknown[] => {
363  let offset = (page - 1) * pageSize;
364  return offset + pageSize >= source.length
365    ? source.slice(offset, source.length)
366    : source.slice(offset, offset + pageSize);
367};
368
369const PAGE_SIZE: number = 50_0000;
370export let postMessage = (id: unknown, action: string, results: Array<unknown>, pageSize: number = PAGE_SIZE): void => {
371  if (results.length > pageSize) {
372    let pageCount = Math.ceil(results.length / pageSize);
373    for (let i = 1; i <= pageCount; i++) {
374      let tag = 'start';
375      if (i === 1) {
376        tag = 'start';
377      } else if (i === pageCount) {
378        tag = 'end';
379      } else {
380        tag = 'sending';
381      }
382      let msg = new Msg();
383      msg.tag = tag;
384      msg.index = i;
385      msg.isSending = tag !== 'end';
386      msg.data = pagination(i, PAGE_SIZE, results);
387      self.postMessage({
388        id: id,
389        action: action,
390        isSending: msg.tag !== 'end',
391        results: msg,
392      });
393    }
394    results.length = 0;
395  } else {
396    let msg = new Msg();
397    msg.tag = 'end';
398    msg.index = 0;
399    msg.isSending = false;
400    msg.data = results;
401    self.postMessage({ id: id, action: action, results: msg });
402    results.length = 0;
403  }
404};
405export let translateJsonString = (str: string): string => {
406  return str //   .padding
407    .replace(/[\t|\r|\n]/g, '')
408    .replace(/\\/g, '\\\\');
409};
410
411export let convertJSON = (arrBuf: ArrayBuffer | Array<unknown>): unknown[] => {
412  if (arrBuf instanceof ArrayBuffer) {
413    let string = dec.decode(arrBuf);
414    let jsonArray = [];
415    string = string.substring(string.indexOf('\n') + 1);
416    if (!string) {
417    } else {
418      let parse;
419      let tansStr = translateJsonString(string);
420      try {
421        parse = JSON.parse(translateJsonString(string));
422      } catch {
423        tansStr = tansStr.replace(/[^\x20-\x7E]/g, '?'); //匹配乱码字符,将其转换为?
424        parse = JSON.parse(tansStr);
425      }
426      let columns = parse.columns;
427      let values = parse.values;
428      for (let i = 0; i < values.length; i++) {
429        let object = {};
430        for (let j = 0; j < columns.length; j++) {
431          //@ts-ignore
432          object[columns[j]] = values[i][j];
433        }
434        jsonArray.push(object);
435      }
436    }
437    return jsonArray;
438  } else {
439    return arrBuf;
440  }
441};
442
443export let getByteWithUnit = (bytes: number): string => {
444  if (bytes < 0) {
445    return '-' + getByteWithUnit(Math.abs(bytes));
446  }
447  let currentBytes = bytes;
448  let kb1 = 1 << 10;
449  let mb = (1 << 10) << 10;
450  let gb = ((1 << 10) << 10) << 10; // 1 gb
451  let res = '';
452  if (currentBytes > gb) {
453    res += (currentBytes / gb).toFixed(2) + ' GB';
454  } else if (currentBytes > mb) {
455    res += (currentBytes / mb).toFixed(2) + ' MB';
456  } else if (currentBytes > kb1) {
457    res += (currentBytes / kb1).toFixed(2) + ' KB';
458  } else {
459    res += Math.round(currentBytes) + ' byte';
460  }
461  return res;
462};
463
464export let getTimeString = (ns: number): string => {
465  let currentNs = ns;
466  let hour1 = 3600_000_000_000;
467  let minute1 = 60_000_000_000;
468  let second1 = 1_000_000_000;
469  let millisecond1 = 1_000_000;
470  let microsecond1 = 1_000;
471  let res = '';
472  if (currentNs >= hour1) {
473    res += Math.floor(currentNs / hour1) + 'h ';
474    currentNs = currentNs - Math.floor(currentNs / hour1) * hour1;
475  }
476  if (currentNs >= minute1) {
477    res += Math.floor(currentNs / minute1) + 'm ';
478    currentNs = currentNs - Math.floor(ns / minute1) * minute1;
479  }
480  if (currentNs >= second1) {
481    res += Math.floor(currentNs / second1) + 's ';
482    currentNs = currentNs - Math.floor(currentNs / second1) * second1;
483  }
484  if (currentNs >= millisecond1) {
485    res += Math.floor(currentNs / millisecond1) + 'ms ';
486    currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1;
487  }
488  if (currentNs >= microsecond1) {
489    res += Math.floor(currentNs / microsecond1) + 'μs ';
490    currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1;
491  }
492  if (currentNs > 0) {
493    res += currentNs + 'ns ';
494  }
495  if (res === '') {
496    res = ns + '';
497  }
498  return res;
499};
500
501export function getProbablyTime(ns: number): string {
502  let currentNs = ns;
503  let hour1 = 3600_000_000_000;
504  let minute1 = 60_000_000_000;
505  let second1 = 1_000_000_000;
506  let millisecond1 = 1_000_000;
507  let microsecond1 = 1_000;
508  let res = '';
509  if (currentNs >= hour1) {
510    res += (currentNs / hour1).toFixed(2) + 'h ';
511  } else if (currentNs >= minute1) {
512    res += (currentNs / minute1).toFixed(2) + 'm ';
513  } else if (currentNs >= second1) {
514    res += (currentNs / second1).toFixed(2) + 's ';
515  } else if (currentNs >= millisecond1) {
516    res += (currentNs / millisecond1).toFixed(2) + 'ms ';
517  } else if (currentNs >= microsecond1) {
518    res += (currentNs / microsecond1).toFixed(2) + 'μs ';
519  } else if (currentNs > 0) {
520    res += currentNs.toFixed(0) + 'ns ';
521  } else if (res === '') {
522    res = ns + '';
523  }
524  return res;
525}
526
527export function getThreadUsageProbablyTime(ns: number): string {
528  let currentNs = ns;
529  let microsecond1 = 1_000;
530  let res = '';
531  if (currentNs > 0) {
532    res += (currentNs / microsecond1).toFixed(2);
533  } else if (res === '') {
534    res = ns + '';
535  }
536  return res;
537}
538
539export function timeMsFormat2p(timeNs: number): string {
540  let currentNs = timeNs;
541  let oneHour = 3600_000;
542  let oneMinute1 = 60_000;
543  let oneSecond = 1_000; // 1 second
544  let commonResult = '';
545  if (currentNs >= oneHour) {
546    commonResult += Math.floor(currentNs / oneHour).toFixed(2) + 'h';
547    return commonResult;
548  }
549  if (currentNs >= oneMinute1) {
550    commonResult += Math.floor(currentNs / oneMinute1).toFixed(2) + 'min';
551    return commonResult;
552  }
553  if (currentNs >= oneSecond) {
554    commonResult += Math.floor(currentNs / oneSecond).toFixed(2) + 's';
555    return commonResult;
556  }
557  if (currentNs > 0) {
558    commonResult += currentNs.toFixed(2) + 'ms';
559    return commonResult;
560  }
561  if (commonResult === '') {
562    commonResult = '0s';
563  }
564  return commonResult;
565}
566
567export function formatRealDate(date: Date, fmt: string): string {
568  let obj = {
569    'M+': date.getMonth() + 1,
570    'd+': date.getDate(),
571    'h+': date.getHours(),
572    'm+': date.getMinutes(),
573    's+': date.getSeconds(),
574    'q+': Math.floor((date.getMonth() + 3) / 3),
575    S: date.getMilliseconds(),
576  };
577  if (/(y+)/.test(fmt)) {
578    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
579  }
580  for (let key in obj) {
581    if (new RegExp('(' + key + ')').test(fmt)) {
582      // @ts-ignore
583      fmt = fmt.replace(
584        RegExp.$1,
585        // @ts-ignore
586        RegExp.$1.length === 1 ? obj[key] : ('00' + obj[key]).substr(('' + obj[key]).length)
587      );
588    }
589  }
590  return fmt;
591}
592
593export function formatRealDateMs(timeNs: number): string {
594  return formatRealDate(new Date(timeNs / 1000000), 'MM-dd hh:mm:ss.S');
595}
596
597export class JsProfilerSymbol {
598  id: number = 0;
599  nameId: number = 0;
600  name: string = '';
601  scriptId: number = 0;
602  urlId: number = 0;
603  url: string = '';
604  line: number = 0;
605  column: number = 0;
606  hitCount: number = 0;
607  childrenString?: string;
608  childrenIds: Array<number> = [];
609  children?: Array<JsProfilerSymbol>;
610  parentId: number = 0;
611  depth: number = -1;
612  cpuProfilerData?: JsProfilerSymbol;
613
614  public clone(): JsProfilerSymbol {
615    const cloneSymbol = new JsProfilerSymbol();
616    cloneSymbol.name = this.name;
617    cloneSymbol.url = this.url;
618    cloneSymbol.hitCount = this.hitCount;
619    cloneSymbol.children = [];
620    cloneSymbol.childrenIds = [];
621    cloneSymbol.parentId = this.parentId;
622    cloneSymbol.depth = this.depth;
623    cloneSymbol.cpuProfilerData = this.cpuProfilerData;
624    return cloneSymbol;
625  }
626}
627
628export class HeapTreeDataBean {
629  MoudleName: string | undefined;
630  AllocationFunction: string | undefined;
631  symbolId: number = 0;
632  fileId: number = 0;
633  startTs: number = 0;
634  endTs: number = 0;
635  eventType: string | undefined;
636  depth: number = 0;
637  heapSize: number = 0;
638  eventId: number = 0;
639  addr: string = '';
640  callChinId: number = 0;
641}
642
643export class PerfCall {
644  sampleId: number = 0;
645  depth: number = 0;
646  name: string = '';
647}
648
649export class FileCallChain {
650  callChainId: number = 0;
651  depth: number = 0;
652  symbolsId: number = 0;
653  pathId: number = 0;
654  ip: string = '';
655  isThread: boolean = false;
656}
657
658export class DataCache {
659  public static instance: DataCache | undefined;
660  public dataDict = new Map<number, string>();
661  public eBpfCallChainsMap = new Map<number, Array<FileCallChain>>();
662  public nmFileDict = new Map<number, string>();
663  public nmHeapFrameMap = new Map<number, Array<HeapTreeDataBean>>();
664  public perfCountToMs = 1; // 1000 / freq
665  public perfCallChainMap: Map<number, PerfCall> = new Map<number, PerfCall>();
666  public jsCallChain: Array<JsProfilerSymbol> | undefined;
667  public jsSymbolMap = new Map<number, JsProfilerSymbol>();
668
669  public static getInstance(): DataCache {
670    if (!this.instance) {
671      this.instance = new DataCache();
672    }
673    return this.instance;
674  }
675
676  public clearAll(): void {
677    if (this.dataDict) {
678      this.dataDict.clear();
679    }
680    this.clearEBpf();
681    this.clearNM();
682    this.clearPerf();
683    this.clearJsCache();
684  }
685
686  public clearNM(): void {
687    this.nmFileDict.clear();
688    this.nmHeapFrameMap.clear();
689  }
690
691  public clearEBpf(): void {
692    this.eBpfCallChainsMap.clear();
693  }
694
695  public clearJsCache(): void {
696    if (this.jsCallChain) {
697      this.jsCallChain.length = 0;
698    }
699    this.jsSymbolMap.clear();
700  }
701
702  public clearPerf(): void {
703    this.perfCallChainMap.clear();
704  }
705}
706
707export class InitAnalysis {
708  public static instance: InitAnalysis | undefined;
709  public isInitAnalysis: boolean = true;
710  public static getInstance(): InitAnalysis {
711    if (!this.instance) {
712      this.instance = new InitAnalysis();
713    }
714    return this.instance;
715  }
716}
717
718interface perfAsyncList {
719  tid?: number;
720  pid?: number;
721  time?: number;
722  symbol?: string;
723  traceid?: string;
724  eventCount?: number;
725  sampleCount?: number;
726  jsFuncName?: string;
727  callerCallchainid?: number;
728  calleeCallchainid?: number;
729  asyncFuncName?: string;
730  eventType?: string;
731  children?: Array<perfAsyncList>;
732  eventTypeId?: number;
733  symbolName?: string;
734  callerCallStack?: Array<perfAsyncList>;
735  calleeCallStack?: Array<perfAsyncList>;
736  callStackList?: Array<perfAsyncList>;
737  parent?: perfAsyncList;
738  isProcess?: boolean;
739  isThread?: boolean;
740  depth?: number;
741  isSearch?: boolean;
742  isJsStack?: boolean;
743  lib?: string;
744  isChartSelectParent?: boolean;
745  isChartSelect?: boolean;
746  isDraw?: boolean;
747  drawDur?: number;
748  drawEventCount?: number;
749  drawCount?: number;
750  drawSize?: number;
751  searchEventCount?: number;
752  searchCount?: number;
753  searchDur?: number;
754  searchSize?: number;
755  size?: number;
756  count?: number;
757  dur?: number;
758  tsArray?: Array<number>;
759  isCharged?: boolean;
760  addr?: string;
761}
762
763export function dealAsyncData(
764  arr: Array<perfAsyncList>,
765  perfCallChain: object,
766  nmCallChain: Map<number, Array<{ addr: string, depth: number, eventId: number, fileId: number, symbolId: number }>>,
767  dataDict: Map<number, string>,
768  searchValue: string
769): Array<perfAsyncList> {
770  // 转换为小写字符
771  searchValue = searchValue.toLocaleLowerCase();
772  // 循环遍历每一条数据
773  for (let i = 0; i < arr.length; i++) {
774    let flag: boolean = false;
775    // 定义每条数据的调用栈与被调用栈数组
776    arr[i].calleeCallStack! = [];
777    arr[i].callerCallStack! = [];
778    // 从前端缓存的perfcallchain表与native_hook_frame表中拿到calleeId与callerId对应的数据
779    // @ts-ignore
780    let calleeCallChain = perfCallChain[arr[i].calleeCallchainid];
781    let callerCallChain = nmCallChain.get(arr[i].callerCallchainid!)!;
782    // 循环被调用栈数组,拿到该条采样数据对应的所有被调用栈信息
783    for (let j = 0; j < calleeCallChain.length; j++) {
784      let calleeStack: perfAsyncList = {};
785      // 拿到每一层被调用栈栈名
786      calleeStack.symbolName = dataDict.get(calleeCallChain[j].name)!;
787      // 判断该条采样数据的被调用栈链中是否包含用户筛选字段
788      if (calleeStack.symbolName.toLocaleLowerCase().indexOf(searchValue) !== -1) {
789        flag = true;
790      }
791      // 获取calleeCallchainid、depth、eventTypeId、lib、addr
792      calleeStack.calleeCallchainid = arr[i].calleeCallchainid!;
793      calleeStack.depth = calleeCallChain[j].depth;
794      calleeStack.eventTypeId = arr[i].eventTypeId!;
795      calleeStack.lib = calleeCallChain[j].fileName;
796      calleeStack.addr = `${'0x'}${calleeCallChain[j].vaddrInFile.toString(16)}`;
797      // 填充到该条数据的被调用栈数组中
798      arr[i].calleeCallStack!.push(calleeStack);
799    }
800    for (let z = 0; z < callerCallChain.length; z++) {
801      let callerStack: perfAsyncList = {};
802      // 拿到每一层被调用栈栈名
803      callerStack.symbolName = dataDict.get(callerCallChain[z].symbolId)!;
804      // 判断该条采样数据的调用栈链中是否包含用户筛选字段
805      if (callerStack.symbolName.toLocaleLowerCase().indexOf(searchValue) !== -1) {
806        flag = true;
807      }
808      // 获取callerCallchainid、depth、eventTypeId、lib、addr
809      callerStack.callerCallchainid = arr[i].callerCallchainid!;
810      callerStack.depth = callerCallChain[z].depth;
811      callerStack.eventTypeId = arr[i].eventTypeId!;
812      callerStack.addr = callerCallChain[z].addr;
813      callerStack.lib = setFileName(dataDict.get(callerCallChain[z].fileId)!);
814      // 填充到该条数据的调用栈数组中
815      arr[i].callerCallStack!.push(callerStack);
816    }
817    // 若存在用户筛选字段内容,数据进行保留。若不存在,则在返回给前端的数据中删除此条数据,减少前端处理的数据量
818    if (!flag) {
819      arr.splice(i, 1);
820      i--;
821    }
822  }
823  return arr;
824}